cucumber_characteristics 0.0.4 → 0.0.5
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.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.rspec +2 -0
- data/.rubocop.yml +14 -0
- data/README.md +39 -3
- data/Rakefile +45 -1
- data/cucumber_characteristics.gemspec +17 -16
- data/cucumber_version/1.3.5/Gemfile +4 -0
- data/cucumber_version/1.3.5/output_path.rb +1 -0
- data/cucumber_version/2.0.2/Gemfile +4 -0
- data/cucumber_version/2.0.2/output_path.rb +1 -0
- data/cucumber_version/2.1.0/Gemfile +4 -0
- data/cucumber_version/2.1.0/output_path.rb +1 -0
- data/cucumber_version/2.2.0/Gemfile +4 -0
- data/cucumber_version/2.2.0/output_path.rb +1 -0
- data/cucumber_version/2.3.3/Gemfile +4 -0
- data/cucumber_version/2.3.3/output_path.rb +1 -0
- data/features/characteristics/cucumber_step_characteristics.html +204 -54
- data/features/characteristics/cucumber_step_characteristics.json +1 -1
- data/features/step_definitions/fail_steps.rb +1 -1
- data/features/step_definitions/unused_steps.rb +2 -0
- data/features/support/env.rb +2 -0
- data/lib/cucumber_characteristics.rb +10 -5
- data/lib/cucumber_characteristics/autoload.rb +3 -1
- data/lib/cucumber_characteristics/configuration.rb +3 -6
- data/lib/cucumber_characteristics/cucumber_1x_step_patch.rb +38 -0
- data/lib/cucumber_characteristics/cucumber_2x_step_patch.rb +163 -0
- data/lib/cucumber_characteristics/{cucumber_step_patch.rb → cucumber_common_step_patch.rb} +4 -5
- data/lib/cucumber_characteristics/exporter.rb +6 -10
- data/lib/cucumber_characteristics/formatter.rb +0 -23
- data/lib/cucumber_characteristics/profile_data.rb +45 -63
- data/lib/cucumber_characteristics/version.rb +1 -1
- data/lib/cucumber_characteristics/view/step_report.html.haml +35 -33
- data/spec/html_output_spec.rb +179 -0
- data/spec/spec_helper.rb +40 -0
- metadata +71 -51
@@ -1 +1 @@
|
|
1
|
-
[["features/step_definitions/wait_steps.rb:1",{"total_count":
|
1
|
+
[["features/step_definitions/wait_steps.rb:1",{"total_count":33,"passed":{"count":31,"feature_location":{"features/failure.feature:4":[1.002],"features/pending.feature:4":[1.002],"features/scenario.feature:4":[0.501],"features/scenario.feature:5":[0.501],"features/scenario.feature:6":[0.501],"features/scenario_outline.feature:4":[0.101,0.305],"features/scenario_outline.feature:5":[0.2,0.2],"features/scenario_outline.feature:6":[0.305,0.101],"features/scenario_outline.feature:7":[0.2,0.2],"features/scenario_outline_with_background.feature:4":[0.402,0.402],"features/scenario_outline_with_background.feature:7":[0.101,0.501],"features/scenario_outline_with_background.feature:8":[0.2,0.601],"features/scenario_outline_with_background.feature:9":[0.305,0.701],"features/scenario_outline_with_background.feature:10":[0.2,0.2],"features/scenario_with_background.feature:4":[0.402,0.402],"features/scenario_with_background.feature:7":[0.101],"features/scenario_with_background.feature:8":[0.2],"features/scenario_with_background.feature:9":[0.305],"features/scenario_with_background.feature:12":[0.501],"features/scenario_with_background.feature:13":[0.601],"features/scenario_with_background.feature:14":[0.701]}},"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.101,"slowest":1.002,"average":0.3853225806451612,"total_duration":11.944999999999997,"standard_deviation":0.2372462157958885,"variation":0.901,"variance":0.0562857669094693}],["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}],["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}]]
|
@@ -1,8 +1,13 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
require
|
5
|
-
|
1
|
+
require 'cucumber_characteristics/configuration'
|
2
|
+
require 'cucumber_characteristics/cucumber_common_step_patch'
|
3
|
+
if Cucumber::VERSION < '2.0.0'
|
4
|
+
require 'cucumber_characteristics/cucumber_1x_step_patch'
|
5
|
+
else
|
6
|
+
require 'cucumber_characteristics/cucumber_2x_step_patch'
|
7
|
+
end
|
8
|
+
require 'cucumber_characteristics/exporter'
|
9
|
+
require 'cucumber_characteristics/formatter'
|
10
|
+
require 'cucumber_characteristics/profile_data'
|
6
11
|
|
7
12
|
module CucumberCharacteristics
|
8
13
|
class << self
|
@@ -1,15 +1,13 @@
|
|
1
1
|
module CucumberCharacteristics
|
2
|
-
|
3
2
|
class Configuration
|
4
|
-
|
5
3
|
attr_accessor :export_json, :export_html, :target_filename, :relative_path, :precision
|
6
4
|
|
7
5
|
def initialize
|
8
6
|
@export_json = true
|
9
7
|
@export_html = true
|
10
8
|
@precision = 4
|
11
|
-
@target_filename =
|
12
|
-
@relative_path =
|
9
|
+
@target_filename = 'cucumber_step_characteristics'
|
10
|
+
@relative_path = 'features/characteristics'
|
13
11
|
end
|
14
12
|
|
15
13
|
def full_target_filename
|
@@ -18,7 +16,7 @@ module CucumberCharacteristics
|
|
18
16
|
|
19
17
|
def full_dir
|
20
18
|
dir = resolve_path_from_root @relative_path
|
21
|
-
FileUtils.mkdir_p dir unless File.
|
19
|
+
FileUtils.mkdir_p dir unless File.exist? dir
|
22
20
|
dir
|
23
21
|
end
|
24
22
|
|
@@ -31,6 +29,5 @@ module CucumberCharacteristics
|
|
31
29
|
File.expand_path(rel_path, Dir.pwd)
|
32
30
|
end
|
33
31
|
end
|
34
|
-
|
35
32
|
end
|
36
33
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Cucumber
|
2
|
+
class Runtime
|
3
|
+
def scenario_profiles
|
4
|
+
return @scenario_profiles if @scenario_profiles
|
5
|
+
feature_profiles = {}
|
6
|
+
scenarios.each do |f|
|
7
|
+
if f.is_a?(Cucumber::Ast::OutlineTable::ExampleRow)
|
8
|
+
feature_id = f.scenario_outline.file_colon_line
|
9
|
+
feature_profiles[feature_id] ||= { name: f.scenario_outline.name, total_duration: 0, step_count: 0, example_count: 0, examples: {} }
|
10
|
+
example_id = f.name
|
11
|
+
feature_profiles[feature_id][:examples][example_id] = scenario_outline_example_profile(f)
|
12
|
+
else
|
13
|
+
feature_id = f.file_colon_line
|
14
|
+
feature_profiles[feature_id] = scenario_profile(f)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@scenario_profiles = feature_profiles
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def scenario_profile(scenario)
|
23
|
+
scenario_profile = { name: scenario.name, total_duration: 0, step_count: 0 }
|
24
|
+
scenario_profile[:total_duration] = scenario.steps.select { |s| s.status == :passed }.map { |s| s.step_match.duration }.inject(&:+)
|
25
|
+
scenario_profile[:step_count] = scenario.steps.count
|
26
|
+
scenario_profile[:status] = scenario.status
|
27
|
+
scenario_profile
|
28
|
+
end
|
29
|
+
|
30
|
+
def scenario_outline_example_profile(scenario)
|
31
|
+
example_profile = { total_duration: 0, step_count: 0 }
|
32
|
+
example_profile[:total_duration] = scenario.instance_variable_get(:@step_invocations).select { |s| s.status == :passed }.map { |s| s.step_match.duration }.inject(&:+)
|
33
|
+
example_profile[:step_count] = scenario.instance_variable_get(:@step_invocations).count
|
34
|
+
example_profile[:status] = scenario.status
|
35
|
+
example_profile
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Cucumber
|
2
|
+
class StepDefinitionLight
|
3
|
+
unless method_defined?(:file_colon_line)
|
4
|
+
def file_colon_line
|
5
|
+
location.file_colon_line
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Core
|
11
|
+
module Ast
|
12
|
+
module Location
|
13
|
+
# Cucumber::Core::Ast::Location::Precise
|
14
|
+
class Precise
|
15
|
+
unless method_defined?(:file_colon_line)
|
16
|
+
def file_colon_line
|
17
|
+
to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Formatter
|
26
|
+
module LegacyApi
|
27
|
+
module Ast
|
28
|
+
class Scenario
|
29
|
+
attr_accessor :steps, :background_steps
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class RuntimeFacade
|
34
|
+
def scenario_profiles
|
35
|
+
return @scenario_profiles if @scenario_profiles
|
36
|
+
feature_profiles = {}
|
37
|
+
|
38
|
+
assign_steps_to_scenarios!
|
39
|
+
results.scenarios.each do |scenario|
|
40
|
+
# Feature id outline is the top level feature defintion
|
41
|
+
# aggregating up multiple examples
|
42
|
+
feature_id = feature_id(scenario)
|
43
|
+
if outline_feature?(scenario)
|
44
|
+
feature_profiles[feature_id] ||= { name: scenario.name, total_duration: 0, step_count: 0, examples: {} }
|
45
|
+
agg_steps = aggregate_steps(scenario.steps)
|
46
|
+
feature_profiles[feature_id][:total_duration] += agg_steps[:total_duration]
|
47
|
+
feature_profiles[feature_id][:step_count] += agg_steps[:step_count]
|
48
|
+
|
49
|
+
# First order step associations to scenario examples
|
50
|
+
example_id = scenario_from(scenario.steps.first).name.match(/(Examples.*\))/).captures.first
|
51
|
+
feature_profiles[feature_id][:examples][example_id] = { total_duration: 0, step_count: 0 }
|
52
|
+
feature_profiles[feature_id][:examples][example_id] = aggregate_steps(scenario.steps)
|
53
|
+
feature_profiles[feature_id][:examples][example_id][:status] = scenario.status.to_sym
|
54
|
+
else
|
55
|
+
feature_profiles[feature_id] = { name: scenario.name, total_duration: 0, step_count: 0 }
|
56
|
+
feature_profiles[feature_id][:status] = scenario.status.to_sym
|
57
|
+
feature_profiles[feature_id].merge!(aggregate_steps(scenario.steps))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# Collect up background tasks not directly attributable to
|
61
|
+
# specific scenarios
|
62
|
+
feature_files.each do |file|
|
63
|
+
steps = background_steps_for(file)
|
64
|
+
next if steps.empty?
|
65
|
+
feature_id = "#{file}:0 (Background)"
|
66
|
+
feature_profiles[feature_id] = { name: 'Background', total_duration: 0, step_count: 0 }
|
67
|
+
feature_profiles[feature_id].merge!(aggregate_steps(steps))
|
68
|
+
feature_profiles[feature_id][:status] =
|
69
|
+
steps.map(&:status).uniq.join(',')
|
70
|
+
end
|
71
|
+
|
72
|
+
@scenario_profiles = feature_profiles
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def feature_id(scenario)
|
78
|
+
if outline_feature?(scenario)
|
79
|
+
scenario_outline_to_feature_id(scenario)
|
80
|
+
else
|
81
|
+
scenario.location.to_s
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def outline_feature?(scenario)
|
86
|
+
scenario.name =~ /Examples \(#\d+\)$/
|
87
|
+
end
|
88
|
+
|
89
|
+
def outline_step?(step)
|
90
|
+
step.step.class == Cucumber::Core::Ast::ExpandedOutlineStep
|
91
|
+
end
|
92
|
+
|
93
|
+
def background_step?(step)
|
94
|
+
!step.background.nil?
|
95
|
+
end
|
96
|
+
|
97
|
+
def scenario_from(step)
|
98
|
+
if outline_step?(step)
|
99
|
+
# Match directly to scenario by line number
|
100
|
+
scenarios.select do |s|
|
101
|
+
s.location.file == step.location.file && s.location.line == step.location.line
|
102
|
+
end.first
|
103
|
+
else
|
104
|
+
# Match indirectly to preceeding scenario by line number
|
105
|
+
# (explicit sort needed for ruby 2.x)
|
106
|
+
scenarios.select { |s| s.location.file == step.location.file && s.location.line < step.location.line }.sort { |a, b| a.location.line <=> b.location.line }.last
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def background_steps_for(scenario_file)
|
111
|
+
steps.select do |s|
|
112
|
+
s.location.file == scenario_file &&
|
113
|
+
background_step?(s)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def assign_steps_to_scenarios!
|
118
|
+
steps.each do |step|
|
119
|
+
scenario = scenario_from(step)
|
120
|
+
if scenario
|
121
|
+
scenario.steps ||= []
|
122
|
+
scenario.steps << step
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
Feature = Struct.new(:name, :line) #=> Customer
|
128
|
+
def scenario_outlines_from_file(file)
|
129
|
+
count = 1
|
130
|
+
results = []
|
131
|
+
File.open(file).each_line do |li|
|
132
|
+
results << Feature.new(li, count) if li[/^\s*Scenario Outline:/]
|
133
|
+
count += 1
|
134
|
+
end
|
135
|
+
results
|
136
|
+
end
|
137
|
+
|
138
|
+
# List of scenario name and lines from file
|
139
|
+
def scenario_outlines(file)
|
140
|
+
@feature_outlines ||= {}
|
141
|
+
return @feature_outlines[file] if @feature_outlines[file]
|
142
|
+
@feature_outlines[file] = scenario_outlines_from_file(file)
|
143
|
+
end
|
144
|
+
|
145
|
+
def scenario_outline_to_feature_id(scenario)
|
146
|
+
scenarios = scenario_outlines(scenario.location.file)
|
147
|
+
scenario_outline = scenarios.select { |s| s.line < scenario.location.line }
|
148
|
+
scenario_outline = scenario_outline.sort { |a, b| a.line <=> b.line }.last
|
149
|
+
"#{scenario.location.file}:#{scenario_outline.line}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def feature_files
|
153
|
+
scenarios.map { |s| s.location.file }.uniq
|
154
|
+
end
|
155
|
+
|
156
|
+
def aggregate_steps(steps)
|
157
|
+
{ total_duration: steps.reject { |s| [:skipped, :undefined].include?(s.status) }.map { |s| s.duration.nanoseconds.to_f / 1_000_000_000 }.inject(&:+),
|
158
|
+
step_count: steps.count }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -1,14 +1,14 @@
|
|
1
|
-
#http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i
|
1
|
+
# http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i
|
2
2
|
|
3
3
|
module Cucumber
|
4
4
|
class StepMatch
|
5
|
-
if
|
5
|
+
if method_defined?(:invoke)
|
6
6
|
old_invoke = instance_method(:invoke)
|
7
7
|
attr_reader :duration
|
8
8
|
|
9
|
-
define_method(:invoke) do |
|
9
|
+
define_method(:invoke) do |multiline_arg|
|
10
10
|
start_time = Time.now
|
11
|
-
ret = old_invoke.bind(self).(multiline_arg)
|
11
|
+
ret = old_invoke.bind(self).call(multiline_arg)
|
12
12
|
@duration = Time.now - start_time
|
13
13
|
ret
|
14
14
|
end
|
@@ -16,7 +16,6 @@ module Cucumber
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
19
|
module Cucumber
|
21
20
|
module Ast
|
22
21
|
class StepInvocation
|
@@ -2,9 +2,7 @@ require 'haml'
|
|
2
2
|
require 'digest/md5'
|
3
3
|
|
4
4
|
module CucumberCharacteristics
|
5
|
-
|
6
5
|
class Exporter
|
7
|
-
|
8
6
|
attr_reader :profile
|
9
7
|
def initialize(profile)
|
10
8
|
@profile = profile
|
@@ -14,11 +12,11 @@ module CucumberCharacteristics
|
|
14
12
|
def export
|
15
13
|
filename = @config.full_target_filename
|
16
14
|
if @config.export_html
|
17
|
-
File.open(filename+'.html', 'w') { |file| file.write(to_html) }
|
15
|
+
File.open(filename + '.html', 'w') { |file| file.write(to_html) }
|
18
16
|
puts "Step characteristics report written to #{filename}.html"
|
19
17
|
end
|
20
18
|
if @config.export_json
|
21
|
-
File.open(filename+'.json', 'w') { |file| file.write(to_json) }
|
19
|
+
File.open(filename + '.json', 'w') { |file| file.write(to_json) }
|
22
20
|
puts "Step characteristics report written to #{filename}.json"
|
23
21
|
end
|
24
22
|
end
|
@@ -36,20 +34,18 @@ module CucumberCharacteristics
|
|
36
34
|
# HELPERS
|
37
35
|
|
38
36
|
def format_ts(t)
|
39
|
-
t ?
|
37
|
+
t ? format("%0.#{@config.precision}f", t) : '-'
|
40
38
|
end
|
41
39
|
|
42
40
|
def format_step_usage(step_feature_data)
|
43
41
|
step_feature_data[:feature_location].map do |location, timings|
|
44
|
-
|
42
|
+
location.to_s + (timings.count > 1 ? " (x#{timings.count})" : '')
|
45
43
|
end.join("\n")
|
46
44
|
end
|
47
45
|
|
48
46
|
def step_status_summary(profile)
|
49
47
|
status = profile.step_count_by_status
|
50
|
-
status.keys.sort.map{|s| status[s]> 0 ? "#{s.capitalize}: #{status[s]}" : nil}.compact.join(', ')
|
48
|
+
status.keys.sort.map { |s| status[s] > 0 ? "#{s.capitalize}: #{status[s]}" : nil }.compact.join(', ')
|
51
49
|
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
50
|
+
end
|
55
51
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module CucumberCharacteristics
|
2
|
-
|
3
2
|
class Formatter
|
4
|
-
|
5
3
|
def initialize(runtime, io, options)
|
6
4
|
@runtime = runtime
|
7
5
|
@io = io
|
@@ -9,30 +7,9 @@ module CucumberCharacteristics
|
|
9
7
|
@features = {}
|
10
8
|
end
|
11
9
|
|
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
|
-
|
30
10
|
def after_features(features)
|
31
11
|
profile = ProfileData.new(@runtime, features)
|
32
12
|
Exporter.new(profile).export
|
33
13
|
end
|
34
|
-
|
35
14
|
end
|
36
|
-
|
37
|
-
|
38
15
|
end
|
@@ -1,13 +1,15 @@
|
|
1
|
-
|
1
|
+
require 'pp'
|
2
2
|
|
3
|
+
module CucumberCharacteristics
|
3
4
|
class ProfileData
|
5
|
+
CUCUMBER_VERSION = Gem::Version.new(Cucumber::VERSION)
|
4
6
|
|
5
7
|
extend Forwardable
|
6
8
|
|
7
9
|
def_delegators :@runtime, :scenarios, :steps
|
8
10
|
attr_reader :duration
|
9
11
|
|
10
|
-
STATUS_ORDER = {passed: 0, failed: 2000, skipped: 1000, undefined: 500}
|
12
|
+
STATUS_ORDER = { passed: 0, failed: 2000, skipped: 1000, undefined: 500 }.freeze
|
11
13
|
|
12
14
|
STATUS = STATUS_ORDER.keys
|
13
15
|
|
@@ -18,82 +20,64 @@ module CucumberCharacteristics
|
|
18
20
|
end
|
19
21
|
|
20
22
|
def ambiguous_count
|
21
|
-
@runtime.steps.count{|s| ambiguous?(s)}
|
23
|
+
@runtime.steps.count { |s| ambiguous?(s) }
|
22
24
|
end
|
23
25
|
|
24
26
|
def unmatched_steps
|
25
27
|
unmatched = {}
|
26
28
|
@runtime.unmatched_step_definitions.each do |u|
|
27
|
-
|
29
|
+
location = u.file_colon_line
|
30
|
+
unmatched[location] = u.regexp_source
|
28
31
|
end
|
29
32
|
unmatched.sort
|
30
33
|
end
|
31
34
|
|
32
|
-
def
|
35
|
+
def unmatched_steps?
|
33
36
|
unmatched_steps.count > 0
|
34
37
|
end
|
35
38
|
|
36
39
|
def feature_profiles
|
37
|
-
feature_profiles
|
38
|
-
@runtime.
|
39
|
-
|
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.instance_variable_get(:@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.instance_variable_get(:@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)
|
40
|
+
return @feature_profiles if @feature_profiles
|
41
|
+
feature_profiles = @runtime.scenario_profiles
|
42
|
+
@feature_profiles = with_feature_calculations(feature_profiles)
|
56
43
|
end
|
57
44
|
|
58
45
|
def with_feature_calculations(feature_profiles)
|
59
46
|
feature_profiles.each do |feature, meta|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
end
|
47
|
+
next unless meta[:examples]
|
48
|
+
feature_profiles[feature][:example_count] = meta[:examples].keys.count
|
49
|
+
feature_profiles[feature][:total_duration] = meta[:examples].map { |_e, m| m[:total_duration] || 0 }.inject(&:+)
|
50
|
+
feature_profiles[feature][:step_count] = meta[:examples].map { |_e, m| m[:step_count] }.inject(&:+)
|
51
|
+
feature_profiles[feature][:examples] = feature_profiles[feature][:examples].sort_by { |_k, v| v[:total_duration] }.reverse
|
52
|
+
feature_profiles[feature][:status] = if meta[:examples].all? { |_e, m| m[:status] == :passed }
|
53
|
+
:passed
|
54
|
+
elsif meta[:examples].any? { |_e, m| m[:status] == :failed }
|
55
|
+
:failed
|
56
|
+
elsif meta[:examples].any? { |_e, m| m[:status] == :skipped }
|
57
|
+
:skipped
|
58
|
+
else
|
59
|
+
:unknown
|
60
|
+
end
|
75
61
|
end
|
76
|
-
feature_profiles.sort_by{|
|
62
|
+
feature_profiles.sort_by { |_k, v| (STATUS_ORDER[v[:status]] || 0) + (v[:total_duration] || 0) }.reverse
|
77
63
|
end
|
78
64
|
|
79
65
|
def step_profiles
|
80
66
|
step_profiles = {}
|
81
67
|
@runtime.steps.each do |s|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
end
|
68
|
+
next if ambiguous?(s)
|
69
|
+
step_name = s.status == :undefined ? s.name : s.step_match.step_definition.file_colon_line
|
70
|
+
# Initialize data structure
|
71
|
+
step_profiles[step_name] ||= { total_count: 0 }
|
72
|
+
STATUS.each { |status| step_profiles[step_name][status] ||= { count: 0, feature_location: {} } }
|
73
|
+
feature_location = s.file_colon_line
|
74
|
+
step_profiles[step_name][s.status][:count] += 1
|
75
|
+
step_profiles[step_name][:total_count] += 1
|
76
|
+
step_profiles[step_name][s.status][:feature_location][feature_location] ||= []
|
77
|
+
next unless s.status != :undefined
|
78
|
+
step_profiles[step_name][:regexp] = s.step_match.step_definition.regexp_source
|
79
|
+
if s.status == :passed
|
80
|
+
step_profiles[step_name][s.status][:feature_location][feature_location] << s.step_match.duration
|
97
81
|
end
|
98
82
|
end
|
99
83
|
with_step_calculations(step_profiles)
|
@@ -118,18 +102,18 @@ module CucumberCharacteristics
|
|
118
102
|
step_profiles[step][:variation] = step_profiles[step][:slowest] - step_profiles[step][:fastest]
|
119
103
|
step_profiles[step][:total_duration] = timings.inject(:+)
|
120
104
|
step_profiles[step][:average] = step_profiles[step][:total_duration] / meta[:passed][:count]
|
121
|
-
sum = timings.inject(0){|accum, i| accum +(i-step_profiles[step][:average])**2 }
|
122
|
-
step_profiles[step][:variance] = sum/
|
123
|
-
step_profiles[step][:standard_deviation] = Math.sqrt(
|
105
|
+
sum = timings.inject(0) { |accum, i| accum + (i - step_profiles[step][:average])**2 }
|
106
|
+
step_profiles[step][:variance] = sum / timings.length.to_f
|
107
|
+
step_profiles[step][:standard_deviation] = Math.sqrt(step_profiles[step][:variance])
|
124
108
|
end
|
125
|
-
step_profiles.sort_by{|
|
109
|
+
step_profiles.sort_by { |_k, v| v[:total_duration] || 0 }.reverse
|
126
110
|
end
|
127
111
|
|
128
112
|
def step_duration
|
129
113
|
step_duration = []
|
130
|
-
step_profiles.each do |
|
114
|
+
step_profiles.each do |_step, meta|
|
131
115
|
STATUS.each do |status|
|
132
|
-
meta[status][:feature_location].each do |
|
116
|
+
meta[status][:feature_location].each do |_location, timings|
|
133
117
|
step_duration << timings
|
134
118
|
end
|
135
119
|
end
|
@@ -162,7 +146,5 @@ module CucumberCharacteristics
|
|
162
146
|
end
|
163
147
|
status
|
164
148
|
end
|
165
|
-
|
166
149
|
end
|
167
|
-
|
168
150
|
end
|