cucumber_characteristics 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -15,5 +15,3 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
-
19
- features/characteristics/
data/README.md CHANGED
@@ -33,6 +33,7 @@ You can configure the export of step characteristics via the following (defaults
33
33
  CucumberCharacteristics.configure do |config|
34
34
  config.export_json = true
35
35
  config.export_html = true
36
+ config.precision = 4
36
37
  config.target_filename = 'cucumber_step_characteristics'
37
38
  config.relative_path = 'features/characteristics'
38
39
  end
@@ -48,6 +49,8 @@ depending on the options specified.
48
49
 
49
50
  The JSON option is provided for convenience in case there is a further use case/analysis required that is not provided by the gem.
50
51
 
52
+ An example can be found [here](features/characteristics/cucumber_step_characteristics.html)
53
+
51
54
  ## Problem
52
55
 
53
56
  The formatting hooks on the face of it provide the necessary event points to profile any given feature file.
@@ -0,0 +1,4 @@
1
+ Feature: As a user I want to understand where my tests are spending their time in a scenaio with an ambiguous step
2
+
3
+ Scenario: Timings for normal scenario
4
+ Given I wait some seconds
@@ -0,0 +1,358 @@
1
+ <!DOCTYPE html>
2
+ <html lang='en'>
3
+ <head>
4
+ <meta charset='utf-8'>
5
+ <meta content='IE=edge' http-equiv='X-UA-Compatible'>
6
+ <meta content='width=device-width, initial-scale=1' name='viewport'>
7
+ <meta content='Cucumber Step Characteristics' name='description'>
8
+ <title>Cucumber Step Characteristics</title>
9
+ <link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css' rel='stylesheet'>
10
+ <link href='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css' rel='stylesheet'>
11
+ <!-- Custom styles for this layout -->
12
+ <style>
13
+ body {
14
+ min-height: 2000px;
15
+ padding-top: 70px;
16
+ }
17
+
18
+ td {
19
+ white-space: nowrap;
20
+ }
21
+
22
+ table.tablesorter thead tr .headerSortUp {
23
+ background-color: #8dbdd8;
24
+ }
25
+ table.tablesorter thead tr .headerSortDown {
26
+ background-color: #8dbdd8;
27
+ }
28
+ </style>
29
+ <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
30
+ <!--[if lt IE 9]>
31
+ <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
32
+ <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
33
+ <![endif]-->
34
+ </head>
35
+ <body>
36
+ <div class='container'>
37
+ <div class='page-header'>
38
+ <h1>Cucumber step characteristics</h1>
39
+ </div>
40
+ <div class='alert alert-info'>
41
+ <span>
42
+ 10 Scenarios,
43
+ 36 Steps completed
44
+ <span class='text-muted'>
45
+ (Failed: 2, Passed: 31, Skipped: 2, Undefined: 1)<br>
46
+ </span>
47
+ 1/2 failures due to ambiguous matches<br>
48
+ Test duration 12.0048s.
49
+ <span class='text-muted'>
50
+ (11.9254s steps, 0.0794s non-steps)
51
+ </span>
52
+ </span>
53
+ <span class='text-muted pull-right small'>
54
+ Finished on 2014-06-25 20:13:34 -0400
55
+ </span>
56
+ </div>
57
+ <ul class='nav nav-tabs'>
58
+ <li class='active'>
59
+ <a data-toggle='tab' href='#steps'>Steps</a>
60
+ </li>
61
+ <li>
62
+ <a data-toggle='tab' href='#features'>Features</a>
63
+ </li>
64
+ </ul>
65
+ <div class='tab-content'>
66
+ <div class='tab-pane active' id='steps'>
67
+ <table class='table table-striped table-bordered table-condensed tablesorter' id='profile_table'>
68
+ <thead>
69
+ <tr>
70
+ <th>Step</th>
71
+ <th>Total time</th>
72
+ <th>Passed</th>
73
+ <th>Average</th>
74
+ <th>Fastest</th>
75
+ <th>Slowest</th>
76
+ <th>Variation</th>
77
+ <th>Variance</th>
78
+ <th>Std Deviation</th>
79
+ <th>Skipped</th>
80
+ <th>Error</th>
81
+ <th>Undef</th>
82
+ <th>Total count</th>
83
+ </tr>
84
+ </thead>
85
+ <tbody>
86
+ <tr>
87
+ <td>
88
+ <abbr title='/^I wait ([\d\.]+) seconds$/'>features/step_definitions/wait_steps.rb:1</abbr>
89
+ </td>
90
+ <td>11.9254</td>
91
+ <td>
92
+ <abbr title='features/failure.feature:4&#x000A;features/pending.feature:4&#x000A;features/scenario.feature:4&#x000A;features/scenario.feature:5&#x000A;features/scenario.feature:6&#x000A;features/scenario_outline.feature:4 (x2)&#x000A;features/scenario_outline.feature:5 (x2)&#x000A;features/scenario_outline.feature:6 (x2)&#x000A;features/scenario_outline.feature:7 (x2)&#x000A;features/scenario_outline_with_background.feature:4 (x2)&#x000A;features/scenario_outline_with_background.feature:7 (x2)&#x000A;features/scenario_outline_with_background.feature:8 (x2)&#x000A;features/scenario_outline_with_background.feature:9 (x2)&#x000A;features/scenario_outline_with_background.feature:10 (x2)&#x000A;features/scenario_with_background.feature:4 (x2)&#x000A;features/scenario_with_background.feature:7&#x000A;features/scenario_with_background.feature:8&#x000A;features/scenario_with_background.feature:9&#x000A;features/scenario_with_background.feature:12&#x000A;features/scenario_with_background.feature:13&#x000A;features/scenario_with_background.feature:14'>31</abbr>
93
+ </td>
94
+ <td>0.3847</td>
95
+ <td>0.1006</td>
96
+ <td>1.0004</td>
97
+ <td>0.8998</td>
98
+ <td>0.0562</td>
99
+ <td>0.2371</td>
100
+ <td>
101
+ <abbr title='features/failure.feature:6&#x000A;features/pending.feature:6'>2</abbr>
102
+ </td>
103
+ <td>
104
+ 0
105
+ </td>
106
+ <td>
107
+ 0
108
+ </td>
109
+ <td>33</td>
110
+ </tr>
111
+ <tr>
112
+ <td>
113
+ <abbr title='/^I fail$/'>features/step_definitions/fail_steps.rb:1</abbr>
114
+ </td>
115
+ <td>-</td>
116
+ <td>
117
+ 0
118
+ </td>
119
+ <td>-</td>
120
+ <td>-</td>
121
+ <td>-</td>
122
+ <td>-</td>
123
+ <td>-</td>
124
+ <td>-</td>
125
+ <td>
126
+ 0
127
+ </td>
128
+ <td>
129
+ <abbr title='features/failure.feature:5'>1</abbr>
130
+ </td>
131
+ <td>
132
+ 0
133
+ </td>
134
+ <td>1</td>
135
+ </tr>
136
+ <tr>
137
+ <td>
138
+ <abbr title=''>I call a pending step</abbr>
139
+ </td>
140
+ <td>-</td>
141
+ <td>
142
+ 0
143
+ </td>
144
+ <td>-</td>
145
+ <td>-</td>
146
+ <td>-</td>
147
+ <td>-</td>
148
+ <td>-</td>
149
+ <td>-</td>
150
+ <td>
151
+ 0
152
+ </td>
153
+ <td>
154
+ 0
155
+ </td>
156
+ <td>
157
+ <abbr title='features/pending.feature:5'>1</abbr>
158
+ </td>
159
+ <td>1</td>
160
+ </tr>
161
+ </tbody>
162
+ </table>
163
+ </div>
164
+ <div class='tab-pane' id='features'>
165
+ <table class='table table-striped table-bordered table-condensed' id='feature_table'>
166
+ <thead>
167
+ <tr>
168
+ <th>Feature</th>
169
+ <th>Total time</th>
170
+ <th>Step count</th>
171
+ <th>Status</th>
172
+ <th></th>
173
+ </tr>
174
+ </thead>
175
+ <tbody>
176
+ <tr class='danger'>
177
+ <td>features/failure.feature:3</td>
178
+ <td>1.0004</td>
179
+ <td>3</td>
180
+ <td>failed</td>
181
+ <td>
182
+ </td>
183
+ </tr>
184
+ <tr class='danger'>
185
+ <td>features/ambiguous.feature:3</td>
186
+ <td>-</td>
187
+ <td>1</td>
188
+ <td>failed</td>
189
+ <td>
190
+ </td>
191
+ </tr>
192
+ <tr class='warning'>
193
+ <td>features/pending.feature:3</td>
194
+ <td>1.0004</td>
195
+ <td>3</td>
196
+ <td>undefined</td>
197
+ <td>
198
+ </td>
199
+ </tr>
200
+ <tr class=''>
201
+ <td>features/scenario_outline_with_background.feature:6</td>
202
+ <td>3.6076</td>
203
+ <td>10</td>
204
+ <td>passed</td>
205
+ <td>
206
+ <button class='btn btn-primary' data-target='#ee4ab50980effe45ea18c9afc38e97e5' data-toggle='modal'>
207
+ 2 Examples
208
+ </button>
209
+ </td>
210
+ </tr>
211
+ <tr class=''>
212
+ <td>features/scenario_with_background.feature:11</td>
213
+ <td>2.2036</td>
214
+ <td>4</td>
215
+ <td>passed</td>
216
+ <td>
217
+ </td>
218
+ </tr>
219
+ <tr class=''>
220
+ <td>features/scenario_outline.feature:3</td>
221
+ <td>1.6051</td>
222
+ <td>8</td>
223
+ <td>passed</td>
224
+ <td>
225
+ <button class='btn btn-primary' data-target='#76f61671f111ebbc40b80f6aa9f5f838' data-toggle='modal'>
226
+ 2 Examples
227
+ </button>
228
+ </td>
229
+ </tr>
230
+ <tr class=''>
231
+ <td>features/scenario.feature:3</td>
232
+ <td>1.5057</td>
233
+ <td>3</td>
234
+ <td>passed</td>
235
+ <td>
236
+ </td>
237
+ </tr>
238
+ <tr class=''>
239
+ <td>features/scenario_with_background.feature:6</td>
240
+ <td>1.0026</td>
241
+ <td>4</td>
242
+ <td>passed</td>
243
+ <td>
244
+ </td>
245
+ </tr>
246
+ </tbody>
247
+ </table>
248
+ <div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='ee4ab50980effe45ea18c9afc38e97e5' role='dialog' tabindex='-1'>
249
+ <div class='modal-dialog modal-lg'>
250
+ <div class='modal-content'>
251
+ <div class='modal-header'>
252
+ <h4>features/scenario_outline_with_background.feature:6</h4>
253
+ </div>
254
+ <div class='modal-body'>
255
+ <table class='table table-striped table-bordered table-condensed'>
256
+ <thead>
257
+ <tr>
258
+ <th>Example</th>
259
+ <th>Total time</th>
260
+ <th>Step count</th>
261
+ <th>Status</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ <tr class=''>
266
+ <td>| 0.5 | 0.6 | 0.7 |</td>
267
+ <td>2.4043</td>
268
+ <td>5</td>
269
+ <td>passed</td>
270
+ </tr>
271
+ <tr class=''>
272
+ <td>| 0.1 | 0.2 | 0.3 |</td>
273
+ <td>1.2033</td>
274
+ <td>5</td>
275
+ <td>passed</td>
276
+ </tr>
277
+ </tbody>
278
+ <tfooter>
279
+ <tr>
280
+ <th></th>
281
+ <th>3.6076</th>
282
+ <th>10</th>
283
+ <th>passed</th>
284
+ </tr>
285
+ </tfooter>
286
+ </table>
287
+ </div>
288
+ <div class='modal-footer'>
289
+ <button class='btn btn-default' data-dismiss='modal' type='button'>
290
+ Close
291
+ </button>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ </div>
296
+ <div aria-hidden='true' aria-labelledby='myLargeModalLabel' class='modal fade bs-example-modal-lg' id='76f61671f111ebbc40b80f6aa9f5f838' role='dialog' tabindex='-1'>
297
+ <div class='modal-dialog modal-lg'>
298
+ <div class='modal-content'>
299
+ <div class='modal-header'>
300
+ <h4>features/scenario_outline.feature:3</h4>
301
+ </div>
302
+ <div class='modal-body'>
303
+ <table class='table table-striped table-bordered table-condensed'>
304
+ <thead>
305
+ <tr>
306
+ <th>Example</th>
307
+ <th>Total time</th>
308
+ <th>Step count</th>
309
+ <th>Status</th>
310
+ </tr>
311
+ </thead>
312
+ <tbody>
313
+ <tr class=''>
314
+ <td>| 0.3 | 0.2 | 0.1 |</td>
315
+ <td>0.8025</td>
316
+ <td>4</td>
317
+ <td>passed</td>
318
+ </tr>
319
+ <tr class=''>
320
+ <td>| 0.1 | 0.2 | 0.3 |</td>
321
+ <td>0.8025</td>
322
+ <td>4</td>
323
+ <td>passed</td>
324
+ </tr>
325
+ </tbody>
326
+ <tfooter>
327
+ <tr>
328
+ <th></th>
329
+ <th>1.6051</th>
330
+ <th>8</th>
331
+ <th>passed</th>
332
+ </tr>
333
+ </tfooter>
334
+ </table>
335
+ </div>
336
+ <div class='modal-footer'>
337
+ <button class='btn btn-default' data-dismiss='modal' type='button'>
338
+ Close
339
+ </button>
340
+ </div>
341
+ </div>
342
+ </div>
343
+ </div>
344
+ </div>
345
+ </div>
346
+ </div>
347
+ <script src='https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js'></script>
348
+ <script src='http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js'></script>
349
+ <script src='https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js'></script>
350
+ <script>
351
+ $(document).ready(function()
352
+ {
353
+ $("#profile_table").tablesorter( {sortList: [[1,1],[3,1]], widgets: ['zebra']} );
354
+ }
355
+ );
356
+ </script>
357
+ </body>
358
+ </html>
@@ -0,0 +1 @@
1
+ [["features/step_definitions/wait_steps.rb:1",{"total_count":33,"passed":{"count":31,"feature_location":{"features/failure.feature:4":[1.000400775],"features/pending.feature:4":[1.000400775],"features/scenario.feature:4":[0.501914414],"features/scenario.feature:5":[0.501914414],"features/scenario.feature:6":[0.501914414],"features/scenario_outline.feature:4":[0.100597662,0.30053452],"features/scenario_outline.feature:5":[0.200701096,0.200701096],"features/scenario_outline.feature:6":[0.30053452,0.100597662],"features/scenario_outline.feature:7":[0.200701096,0.200701096],"features/scenario_outline_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_outline_with_background.feature:7":[0.100597662,0.501914414],"features/scenario_outline_with_background.feature:8":[0.200701096,0.600408111],"features/scenario_outline_with_background.feature:9":[0.30053452,0.700576606],"features/scenario_outline_with_background.feature:10":[0.200701096,0.200701096],"features/scenario_with_background.feature:4":[0.400732052,0.400732052],"features/scenario_with_background.feature:7":[0.100597662],"features/scenario_with_background.feature:8":[0.200701096],"features/scenario_with_background.feature:9":[0.30053452],"features/scenario_with_background.feature:12":[0.501914414],"features/scenario_with_background.feature:13":[0.600408111],"features/scenario_with_background.feature:14":[0.700576606]}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":2,"feature_location":{"features/failure.feature:6":[],"features/pending.feature:6":[]}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I wait ([\\d\\.]+) seconds$/","fastest":0.100597662,"slowest":1.000400775,"average":0.3846906050967743,"total_duration":11.925408758000003,"standard_deviation":0.2370893233738576,"variation":0.8998031129999999,"variance":0.05621134725787363}],["features/step_definitions/fail_steps.rb:1",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":1,"feature_location":{"features/failure.feature:5":[]}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":0,"feature_location":{}},"regexp":"/^I fail$/","fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}],["I call a pending step",{"total_count":1,"passed":{"count":0,"feature_location":{}},"failed":{"count":0,"feature_location":{}},"skipped":{"count":0,"feature_location":{}},"undefined":{"count":1,"feature_location":{"features/pending.feature:5":[]}},"fastest":null,"slowest":null,"average":null,"total_duration":null,"standard_deviation":null,"variation":null}]]
@@ -1,6 +1,6 @@
1
1
  Feature: As a user I want to understand where my tests are spending their time in a scenario
2
2
 
3
3
  Scenario: Timings for normal scenario
4
- Given I wait 1 seconds
5
- When I wait 1 seconds
6
- Then I wait 1 seconds
4
+ Given I wait 0.5 seconds
5
+ When I wait 0.5 seconds
6
+ Then I wait 0.5 seconds
@@ -8,4 +8,4 @@ Feature: As a user I want to understand where my tests are spending their time i
8
8
  Examples:
9
9
  | given_wait | when_wait | then_wait |
10
10
  | 0.1 | 0.2 | 0.3 |
11
- | 0.5 | 0.6 | 0.7 |
11
+ | 0.3 | 0.2 | 0.1 |
@@ -0,0 +1,5 @@
1
+ Given(/^I wait some/) do
2
+ end
3
+
4
+ Given(/^I wait some seconds$/) do
5
+ end
@@ -2,11 +2,12 @@ module CucumberCharacteristics
2
2
 
3
3
  class Configuration
4
4
 
5
- attr_accessor :export_json, :export_html, :target_filename, :relative_path
5
+ attr_accessor :export_json, :export_html, :target_filename, :relative_path, :precision
6
6
 
7
7
  def initialize
8
8
  @export_json = true
9
9
  @export_html = true
10
+ @precision = 4
10
11
  @target_filename = 'cucumber_step_characteristics'
11
12
  @relative_path = 'features/characteristics'
12
13
  end
@@ -2,14 +2,16 @@
2
2
 
3
3
  module Cucumber
4
4
  class StepMatch
5
- old_invoke = instance_method(:invoke)
6
- attr_reader :duration
5
+ if self.method_defined?(:invoke)
6
+ old_invoke = instance_method(:invoke)
7
+ attr_reader :duration
7
8
 
8
- define_method(:invoke) do | multiline_arg |
9
- start_time = Time.now
10
- ret = old_invoke.bind(self).(multiline_arg)
11
- @duration = Time.now - start_time
12
- ret
9
+ define_method(:invoke) do | multiline_arg |
10
+ start_time = Time.now
11
+ ret = old_invoke.bind(self).(multiline_arg)
12
+ @duration = Time.now - start_time
13
+ ret
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -20,5 +22,11 @@ module Cucumber
20
22
  class StepInvocation
21
23
  attr_reader :step_match
22
24
  end
25
+
26
+ class OutlineTable
27
+ class ExampleRow
28
+ attr_reader :step_invocations
29
+ end
30
+ end
23
31
  end
24
32
  end
@@ -1,4 +1,5 @@
1
1
  require 'haml'
2
+ require 'digest/md5'
2
3
 
3
4
  module CucumberCharacteristics
4
5
 
@@ -35,7 +36,7 @@ module CucumberCharacteristics
35
36
  # HELPERS
36
37
 
37
38
  def format_ts(t)
38
- t ? sprintf("%0.4f", t) : '-'
39
+ t ? sprintf("%0.#{@config.precision}f", t) : '-'
39
40
  end
40
41
 
41
42
  def format_step_usage(step_feature_data)
@@ -6,8 +6,27 @@ module CucumberCharacteristics
6
6
  @runtime = runtime
7
7
  @io = io
8
8
  @options = options
9
+ @features = {}
9
10
  end
10
11
 
12
+ # def before_feature(scenario)
13
+ # pp scenario.class.name
14
+ # if scenario.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
15
+ # feature_location = scenario.scenario_outline.file_colon_line
16
+ # feature_name = scenario.scenario_outline.name.chomp
17
+ # feature_example = scenario.name
18
+ # @features[feature_location] ||= {}
19
+ # @features[feature_location][feature_example] ||= {start_time: Time.now}
20
+ # else
21
+ # feature_location = scenario.file_colon_line
22
+ # feature_name = scenario.name.chomp
23
+ # @features[feature_location] ||= {start_time: Time.now}
24
+ # end
25
+ # end
26
+
27
+ # def after_feature(scenario)
28
+ # end
29
+
11
30
  def after_features(features)
12
31
  profile = ProfileData.new(@runtime, features)
13
32
  Exporter.new(profile).export
@@ -7,36 +7,103 @@ module CucumberCharacteristics
7
7
  def_delegators :@runtime, :scenarios, :steps
8
8
  attr_reader :duration
9
9
 
10
- STATUS = [:passed, :failed, :skipped, :undefined ]
10
+ STATUS_ORDER = {passed: 0, failed: 2000, skipped: 1000, undefined: 500}
11
+
12
+ STATUS = STATUS_ORDER.keys
11
13
 
12
14
  def initialize(runtime, features)
13
15
  @runtime = runtime
14
16
  @duration = features.duration
17
+ @features = features
18
+ end
19
+
20
+ def ambiguous_count
21
+ @runtime.steps.count{|s| ambiguous?(s)}
22
+ end
23
+
24
+ def unmatched_steps
25
+ unmatched = {}
26
+ @runtime.unmatched_step_definitions.each do |u|
27
+ unmatched[u.file_colon_line] = u.regexp_source
28
+ end
29
+ unmatched.sort
30
+ end
31
+
32
+ def has_unmatched_steps?
33
+ unmatched_steps.count > 0
34
+ end
35
+
36
+ def feature_profiles
37
+ feature_profiles = { }
38
+ @runtime.scenarios.each do |f|
39
+ if f.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
40
+ feature_id = f.scenario_outline.file_colon_line
41
+ feature_profiles[feature_id] ||= {name: f.scenario_outline.name, total_duration: 0, step_count: 0, example_count: 0, examples: {} }
42
+ example_id = f.name
43
+ feature_profiles[feature_id][:examples][example_id] ||= {total_duration: 0, step_count: 0}
44
+ feature_profiles[feature_id][:examples][example_id][:total_duration] = f.step_invocations.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
45
+ feature_profiles[feature_id][:examples][example_id][:step_count] = f.step_invocations.count
46
+ feature_profiles[feature_id][:examples][example_id][:status] = f.status
47
+ else
48
+ feature_id = f.file_colon_line
49
+ feature_profiles[feature_id] = {name: f.name, total_duration: 0, step_count: 0}
50
+ feature_profiles[feature_id][:total_duration] = f.steps.select{|s| s.status == :passed}.map{|s| s.step_match.duration}.inject(&:+)
51
+ feature_profiles[feature_id][:step_count] = f.steps.count
52
+ feature_profiles[feature_id][:status] = f.status
53
+ end
54
+ end
55
+ with_feature_calculations(feature_profiles)
56
+ end
57
+
58
+ def with_feature_calculations(feature_profiles)
59
+ feature_profiles.each do |feature, meta|
60
+ if meta[:examples]
61
+ feature_profiles[feature][:example_count] = meta[:examples].keys.count
62
+ feature_profiles[feature][:total_duration] = meta[:examples].map{|e,m| m[:total_duration]}.inject(&:+)
63
+ feature_profiles[feature][:step_count] = meta[:examples].map{|e,m| m[:step_count]}.inject(&:+)
64
+ feature_profiles[feature][:examples] = feature_profiles[feature][:examples].sort_by{|k, v| v[:total_duration]}.reverse
65
+ feature_profiles[feature][:status] = if meta[:examples].all?{|e,m| m[:status] == :passed}
66
+ :passed
67
+ elsif meta[:examples].any?{|e,m| m[:status] == :failed}
68
+ :failed
69
+ elsif meta[:examples].any?{|e,m| m[:status] == :skipped}
70
+ :skipped
71
+ else
72
+ :unknown
73
+ end
74
+ end
75
+ end
76
+ feature_profiles.sort_by{|k, v| (STATUS_ORDER[v[:status]]||0)+(v[:total_duration] || 0)}.reverse
15
77
  end
16
78
 
17
79
  def step_profiles
18
- #return @step_profiles if @step_profiles
19
80
  step_profiles = {}
20
81
  @runtime.steps.each do |s|
21
- step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
22
- # Initialize data structure
23
- step_profiles[step_name] ||= { :total_count => 0}
24
- STATUS.each {|status| step_profiles[step_name][status] ||= {:count => 0, :feature_location => {} }}
25
- feature_location = s.file_colon_line
26
- step_profiles[step_name][s.status][:count] += 1
27
- step_profiles[step_name][:total_count] += 1
28
- step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
29
- if s.status != :undefined
30
- step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
31
- if s.status == :passed
32
- step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
82
+ unless ambiguous?(s)
83
+ step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
84
+ # Initialize data structure
85
+ step_profiles[step_name] ||= { :total_count => 0}
86
+ STATUS.each {|status| step_profiles[step_name][status] ||= {:count => 0, :feature_location => {} }}
87
+ feature_location = s.file_colon_line
88
+ step_profiles[step_name][s.status][:count] += 1
89
+ step_profiles[step_name][:total_count] += 1
90
+ step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
91
+ if s.status != :undefined
92
+ step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
93
+ if s.status == :passed
94
+ step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
95
+ end
33
96
  end
34
97
  end
35
98
  end
36
- with_calculations(step_profiles)
99
+ with_step_calculations(step_profiles)
37
100
  end
38
101
 
39
- def with_calculations(step_profiles)
102
+ def ambiguous?(step)
103
+ step.status == :failed && step.step_match.step_definition.nil?
104
+ end
105
+
106
+ def with_step_calculations(step_profiles)
40
107
  step_profiles.each do |step, meta|
41
108
  meta.merge!(fastest: nil, slowest: nil, average: nil, total_duration: nil, standard_deviation: nil, variation: nil)
42
109
  next unless meta[:passed][:count] > 0
@@ -55,7 +122,7 @@ module CucumberCharacteristics
55
122
  step_profiles[step][:variance] = sum/(timings.length ).to_f
56
123
  step_profiles[step][:standard_deviation] = Math.sqrt( step_profiles[step][:variance])
57
124
  end
58
- step_profiles
125
+ step_profiles.sort_by{|k, v| v[:total_duration]||0}.reverse
59
126
  end
60
127
 
61
128
  def step_duration
@@ -83,6 +150,10 @@ module CucumberCharacteristics
83
150
  status
84
151
  end
85
152
 
153
+ def step_count(status)
154
+ step_count_by_status[status]
155
+ end
156
+
86
157
  def scenario_count_by_status
87
158
  status = {}
88
159
  @runtime.scenarios.each do |s|
@@ -94,5 +165,4 @@ module CucumberCharacteristics
94
165
 
95
166
  end
96
167
 
97
-
98
168
  end
@@ -1,3 +1,3 @@
1
1
  module CucumberCharacteristics
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -38,63 +38,140 @@
38
38
  #{profile.scenarios.count} Scenarios,
39
39
  #{profile.steps.count} Steps completed
40
40
  %span.text-muted
41
- (#{step_status_summary(profile)})
42
- <br>
41
+ (#{step_status_summary(profile)})<br>
42
+ - if profile.ambiguous_count > 0
43
+ #{profile.ambiguous_count}/#{profile.step_count(:failed)} failures due to ambiguous matches<br>
43
44
  Test duration #{format_ts(profile.duration)}s.
44
45
  %span.text-muted
45
46
  (#{format_ts(profile.step_duration)}s steps, #{format_ts(profile.nonstep_duration)}s non-steps)
46
47
  %span.text-muted.pull-right.small
47
48
  Finished on #{Time.now}
48
49
 
49
- %table.table.table-striped.table-bordered.table-condensed.tablesorter#profile_table
50
- %thead
51
- %tr
52
- %th Step
53
- %th Total time
54
- %th Passed
55
- %th Average
56
- %th Fastest
57
- %th Slowest
58
- %th Variation
59
- %th Variance
60
- %th Std Deviation
61
- %th Skipped
62
- %th Error
63
- %th Undef
64
- %th Total count
65
- %tbody
66
- - profile.step_profiles.each do |step, meta|
67
- %tr
68
- %td
69
- %abbr{:title => "#{meta[:regexp]}"} #{step}
70
- %td #{format_ts(meta[:total_duration])}
71
- %td
72
- - if meta[:passed][:count] > 0
73
- %abbr{:title => "#{ format_step_usage(meta[:passed]) }" } #{meta[:passed][:count]}
74
- - else
75
- 0
76
- %td #{format_ts(meta[:average])}
77
- %td #{format_ts(meta[:fastest])}
78
- %td #{format_ts(meta[:slowest])}
79
- %td #{format_ts(meta[:variation])}
80
- %td #{format_ts(meta[:variance])}
81
- %td #{format_ts(meta[:standard_deviation])}
82
- %td
83
- - if meta[:skipped][:count] > 0
84
- %abbr{:title => "#{ format_step_usage(meta[:skipped]) }" } #{meta[:skipped][:count]}
85
- - else
86
- 0
87
- %td
88
- - if meta[:failed][:count] > 0
89
- %abbr{:title => "#{ format_step_usage(meta[:failed]) }" } #{meta[:failed][:count]}
90
- - else
91
- 0
92
- %td
93
- - if meta[:undefined][:count] > 0
94
- %abbr{:title => "#{ format_step_usage(meta[:undefined]) }" } #{meta[:undefined][:count]}
95
- - else
96
- 0
97
- %td #{meta[:total_count]}
50
+ %ul.nav.nav-tabs
51
+ %li.active
52
+ %a{:href => '#steps', :'data-toggle' => 'tab'} Steps
53
+ %li
54
+ %a{:href => '#features', :'data-toggle' => 'tab'} Features
55
+ - if profile.has_unmatched_steps?
56
+ %li
57
+ %a{:href => '#unused_steps', :'data-toggle' => 'tab'} Unused Steps (#{profile.unmatched_steps.count})
58
+ %div.tab-content
59
+ %div.tab-pane.active#steps
60
+ %table.table.table-striped.table-bordered.table-condensed.tablesorter#profile_table
61
+ %thead
62
+ %tr
63
+ %th Step
64
+ %th Total time
65
+ %th Passed
66
+ %th Average
67
+ %th Fastest
68
+ %th Slowest
69
+ %th Variation
70
+ %th Variance
71
+ %th Std Deviation
72
+ %th Skipped
73
+ %th Error
74
+ %th Undef
75
+ %th Total count
76
+ %tbody
77
+ - profile.step_profiles.each do |step, meta|
78
+ %tr
79
+ %td
80
+ %abbr{:title => "#{meta[:regexp]}"} #{step}
81
+ %td #{format_ts(meta[:total_duration])}
82
+ %td
83
+ - if meta[:passed][:count] > 0
84
+ %abbr{:title => "#{ format_step_usage(meta[:passed]) }" } #{meta[:passed][:count]}
85
+ - else
86
+ 0
87
+ %td #{format_ts(meta[:average])}
88
+ %td #{format_ts(meta[:fastest])}
89
+ %td #{format_ts(meta[:slowest])}
90
+ %td #{format_ts(meta[:variation])}
91
+ %td #{format_ts(meta[:variance])}
92
+ %td #{format_ts(meta[:standard_deviation])}
93
+ %td
94
+ - if meta[:skipped][:count] > 0
95
+ %abbr{:title => "#{ format_step_usage(meta[:skipped]) }" } #{meta[:skipped][:count]}
96
+ - else
97
+ 0
98
+ %td
99
+ - if meta[:failed][:count] > 0
100
+ %abbr{:title => "#{ format_step_usage(meta[:failed]) }" } #{meta[:failed][:count]}
101
+ - else
102
+ 0
103
+ %td
104
+ - if meta[:undefined][:count] > 0
105
+ %abbr{:title => "#{ format_step_usage(meta[:undefined]) }" } #{meta[:undefined][:count]}
106
+ - else
107
+ 0
108
+ %td #{meta[:total_count]}
109
+
110
+ %div.tab-pane#features
111
+ %table.table.table-striped.table-bordered.table-condensed#feature_table
112
+ %thead
113
+ %tr
114
+ %th Feature
115
+ %th Total time
116
+ %th Step count
117
+ %th Status
118
+ %th
119
+ %tbody
120
+ - profile.feature_profiles.each do |feature, meta|
121
+ %tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
122
+ %td #{feature}
123
+ %td #{format_ts(meta[:total_duration])}
124
+ %td #{meta[:step_count]}
125
+ %td #{meta[:status]}
126
+ %td
127
+ - if meta[:examples]
128
+ %button.btn.btn-primary{:'data-toggle'=>'modal', :'data-target'=>"##{Digest::MD5.hexdigest(feature)}"}
129
+ #{meta[:example_count]} Examples
130
+
131
+ - profile.feature_profiles.select{|f,m| m.key?(:examples) }.each do |feature, meta|
132
+ %div{ :class=> 'modal fade bs-example-modal-lg', :tabindex=>"-1", :role=>"dialog", :'aria-labelledby'=>"myLargeModalLabel", :'aria-hidden'=>"true", :id => "#{Digest::MD5.hexdigest(feature)}"}
133
+ %div.modal-dialog.modal-lg
134
+ %div.modal-content
135
+ %div.modal-header
136
+ %h4 #{feature}
137
+ %div.modal-body
138
+ %table.table.table-striped.table-bordered.table-condensed
139
+ %thead
140
+ %tr
141
+ %th Example
142
+ %th Total time
143
+ %th Step count
144
+ %th Status
145
+ %tbody
146
+ - meta[:examples].each do |example, meta|
147
+ %tr{:class => "#{meta[:status] == :failed ? :danger : meta[:status] == :undefined ? :warning : nil }"}
148
+ %td #{example}
149
+ %td #{format_ts(meta[:total_duration])}
150
+ %td #{meta[:step_count]}
151
+ %td #{meta[:status]}
152
+ %tfooter
153
+ %tr
154
+ %th
155
+ %th #{format_ts(meta[:total_duration])}
156
+ %th #{meta[:step_count]}
157
+ %th #{meta[:status]}
158
+ %div.modal-footer
159
+ %button.btn.btn-default{:type=>"button", :'data-dismiss'=>"modal"}
160
+ Close
161
+
162
+ - if profile.has_unmatched_steps?
163
+ %div.tab-pane#unused_steps
164
+ %table.table.table-striped.table-bordered.table-condensed.tablesorter#unmatched_steps_table
165
+ %thead
166
+ %tr
167
+ %th Step Location
168
+ %th Step
169
+ %tbody
170
+ - profile.unmatched_steps.each do |step_location, step|
171
+ %tr
172
+ %td #{step_location}
173
+ %td #{step}
174
+
98
175
  %script{:src => "https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"}
99
176
  %script{:src => "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"}
100
177
  %script{:src => "https://raw.githubusercontent.com/christianbach/tablesorter/master/jquery.tablesorter.min.js"}
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cucumber_characteristics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-06-07 00:00:00.000000000 Z
12
+ date: 2014-06-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: haml
@@ -136,12 +136,16 @@ files:
136
136
  - README.md
137
137
  - Rakefile
138
138
  - cucumber_characteristics.gemspec
139
+ - features/ambiguous.feature
140
+ - features/characteristics/cucumber_step_characteristics.html
141
+ - features/characteristics/cucumber_step_characteristics.json
139
142
  - features/failure.feature
140
143
  - features/pending.feature
141
144
  - features/scenario.feature
142
145
  - features/scenario_outline.feature
143
146
  - features/scenario_outline_with_background.feature
144
147
  - features/scenario_with_background.feature
148
+ - features/step_definitions/ambiguous_steps.rb
145
149
  - features/step_definitions/fail_steps.rb
146
150
  - features/step_definitions/wait_steps.rb
147
151
  - lib/cucumber_characteristics.rb
@@ -179,11 +183,15 @@ signing_key:
179
183
  specification_version: 3
180
184
  summary: Gem to profile cucumber steps and features
181
185
  test_files:
186
+ - features/ambiguous.feature
187
+ - features/characteristics/cucumber_step_characteristics.html
188
+ - features/characteristics/cucumber_step_characteristics.json
182
189
  - features/failure.feature
183
190
  - features/pending.feature
184
191
  - features/scenario.feature
185
192
  - features/scenario_outline.feature
186
193
  - features/scenario_outline_with_background.feature
187
194
  - features/scenario_with_background.feature
195
+ - features/step_definitions/ambiguous_steps.rb
188
196
  - features/step_definitions/fail_steps.rb
189
197
  - features/step_definitions/wait_steps.rb