cucumber_characteristics 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|