cucumber-profiler 1.0.0
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 +18 -0
- data/Gemfile +3 -0
- data/README.textile +29 -0
- data/Rakefile +2 -0
- data/cucumber-profiler.gemspec +19 -0
- data/lib/cucumber-profiler.rb +1 -0
- data/lib/cucumber/formatter/profiler.rb +218 -0
- metadata +68 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.textile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
h1. cucumber-profiler
|
2
|
+
|
3
|
+
_Currently_ _tested_ _against_ _cucumber-1.2.1_. _Is_ _known_ _not_ _to_ _be_ _compatible_ _with_ _some_ _older_ _versions_.
|
4
|
+
|
5
|
+
A way to profile the performance of your cucumber features.
|
6
|
+
|
7
|
+
The profiler groups your cucumber tests by feature. Within a feature, the profiler times each scenario and calculates the mean and standard deviation. Each scenario that is two or more standard deviations above the mean is listed.
|
8
|
+
|
9
|
+
h2. Installation
|
10
|
+
|
11
|
+
1. Put the profiler in your @Gemfile@
|
12
|
+
|
13
|
+
bc. gem cucumber-profiler
|
14
|
+
|
15
|
+
2. Use the @format@ flag to use the formatter
|
16
|
+
|
17
|
+
bc. cucumber --format Cucumber::Formatter::Profiler features
|
18
|
+
|
19
|
+
Alternatively, you can make the profiler your default format by putting the following your @cucumber.yml@ file:
|
20
|
+
|
21
|
+
bc. --format Cucumber::Formatter::Profiler
|
22
|
+
|
23
|
+
_If you use Timecop_: If you use the timecop gem to freeze or change time be sure to also return the
|
24
|
+
time after each of your tests. Not doing so will make it appear as if your tests are taking either
|
25
|
+
far too long or have taken negative time to complete.
|
26
|
+
|
27
|
+
h2. Copyright
|
28
|
+
|
29
|
+
Copyright (c) 2012 Michael Blumberg.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "cucumber-profiler"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ["Michael Blumberg"]
|
6
|
+
s.email = ["mblum14@gmail.com"]
|
7
|
+
s.homepage = "https://github.com/mblum14/cucumber-profiler"
|
8
|
+
s.summary = %q{A way to profile the performance of your cucumber features}
|
9
|
+
s.description = %q{A way to profile the performance of your cucumber features}
|
10
|
+
|
11
|
+
s.rubyforge_project = "cucumber-profiler"
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_dependency('cucumber', ["~> 1.2.1"])
|
19
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'cucumber/formatter/profiler'
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'cucumber/formatter/io'
|
2
|
+
require 'cucumber/formatter/console'
|
3
|
+
|
4
|
+
module Math::Array
|
5
|
+
def sum
|
6
|
+
self.inject(0){ |accum, i| accum + i }
|
7
|
+
end
|
8
|
+
|
9
|
+
def mean
|
10
|
+
self.sum/self.length.to_f
|
11
|
+
end
|
12
|
+
|
13
|
+
def sample_variance
|
14
|
+
m = self.mean
|
15
|
+
sum = self.inject(0) { |accum, i| accum + (i - m) ** 2 }
|
16
|
+
sum / (self.length).to_f
|
17
|
+
end
|
18
|
+
|
19
|
+
def standard_deviation
|
20
|
+
return Math.sqrt(self.sample_variance)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Cucumber
|
25
|
+
module Ast
|
26
|
+
module Benchmark
|
27
|
+
attr_accessor :started_at, :finished_at, :run_time
|
28
|
+
end
|
29
|
+
end
|
30
|
+
module Formatter
|
31
|
+
class Profiler
|
32
|
+
include Console
|
33
|
+
include Io
|
34
|
+
|
35
|
+
attr_reader :step_mother
|
36
|
+
|
37
|
+
def initialize(step_mother, path_or_io, options)
|
38
|
+
@step_mother, @io, @options, @durations = step_mother, ensure_io(path_or_io, "fuubar"), options, []
|
39
|
+
@timed_features = {}
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def after_features(features)
|
44
|
+
@io.puts
|
45
|
+
@io.puts
|
46
|
+
print_summary(features)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def before_feature_element(feature_element)
|
51
|
+
feature_element.extend Cucumber::Ast::Benchmark
|
52
|
+
feature_element.started_at = Time.now
|
53
|
+
@exception_raised = false
|
54
|
+
end
|
55
|
+
|
56
|
+
def after_feature_element(feature_element)
|
57
|
+
feature_element.finished_at = Time.now
|
58
|
+
feature_element.run_time = feature_element.finished_at - feature_element.started_at
|
59
|
+
key = feature_element.feature.title
|
60
|
+
if @timed_features.has_key? key
|
61
|
+
@timed_features[key] = @timed_features[key] << feature_element
|
62
|
+
else
|
63
|
+
@timed_features[key] = [feature_element]
|
64
|
+
end
|
65
|
+
|
66
|
+
progress(:failed) if @exception_raised
|
67
|
+
@exception_raised = false
|
68
|
+
end
|
69
|
+
|
70
|
+
def before_steps(*args)
|
71
|
+
progress(:failed) if @exception_raised
|
72
|
+
@exception_raised = false
|
73
|
+
end
|
74
|
+
|
75
|
+
def after_steps(*args)
|
76
|
+
@exception_raised = false
|
77
|
+
end
|
78
|
+
|
79
|
+
def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
|
80
|
+
progress(status)
|
81
|
+
@status = status
|
82
|
+
end
|
83
|
+
|
84
|
+
def before_outline_table(outline_table)
|
85
|
+
@outline_table = outline_table
|
86
|
+
end
|
87
|
+
|
88
|
+
def after_outline_table(outline_table)
|
89
|
+
@outline_table = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def table_cell_value(value, status)
|
93
|
+
return unless @outline_table
|
94
|
+
status ||= @status
|
95
|
+
progress(status) unless table_header_cell?(status)
|
96
|
+
end
|
97
|
+
|
98
|
+
def exception(*args)
|
99
|
+
@exception_raised = true
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
SUB_SECOND_PRECISION = 5
|
105
|
+
DEFAULT_PRECISION = 2
|
106
|
+
|
107
|
+
def print_summary(features)
|
108
|
+
@timed_features.each_key do |feature_name|
|
109
|
+
print_report(@timed_features[feature_name], feature_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
@io.puts
|
113
|
+
@io.puts
|
114
|
+
print_steps(:pending)
|
115
|
+
print_steps(:failed)
|
116
|
+
print_stats(features, @options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def print_report(feature_elements, feature_name)
|
120
|
+
features_elements = feature_elements.sort_by do |e|
|
121
|
+
e.run_time
|
122
|
+
end.reverse
|
123
|
+
|
124
|
+
times = feature_elements.map { |e| e.run_time }
|
125
|
+
times.extend Math::Array
|
126
|
+
mean = times.mean
|
127
|
+
stddev = times.standard_deviation
|
128
|
+
k = 2
|
129
|
+
feature_elements.reject! { |e| (e.run_time < (mean + k * stddev)) || (stddev == 0) }
|
130
|
+
|
131
|
+
@io.puts "\n\nFeature: #{bold magenta(feature_name)}"
|
132
|
+
@io.puts "#{bold red(feature_elements.size)} of #{bold green(times.size)} scenarios(s) were 2 or greater standard deviations above the mean"
|
133
|
+
@io.puts cyan "#{"Mean execution time:"} #{format_time(mean)}"
|
134
|
+
@io.puts cyan "#{"Standard Deviation:"} #{"%.5f" % stddev}"
|
135
|
+
@io.puts red "WARNING: Slow mean execution time!" if mean > 3
|
136
|
+
|
137
|
+
feature_elements.each_with_index do |example, i|
|
138
|
+
@io.puts cyan("#{i+1}.\t#{format_time(example.run_time)}") + white(" \t#{example.title}")
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
CHARS = {
|
143
|
+
:passed => '.',
|
144
|
+
:failed => 'F',
|
145
|
+
:undefined => 'U',
|
146
|
+
:pending => 'P',
|
147
|
+
:skipped => '-'
|
148
|
+
}
|
149
|
+
|
150
|
+
def progress(status)
|
151
|
+
char = CHARS[status]
|
152
|
+
@io.print(format_string(char, status))
|
153
|
+
@io.flush
|
154
|
+
end
|
155
|
+
|
156
|
+
def table_header_cell?(status)
|
157
|
+
status == :skipped_param
|
158
|
+
end
|
159
|
+
|
160
|
+
def format_time(duration)
|
161
|
+
if duration > 60
|
162
|
+
minutes = duration.to_i / 60
|
163
|
+
seconds = duration - minutes * 60
|
164
|
+
|
165
|
+
red "#{minutes}m #{format_seconds(seconds)}s"
|
166
|
+
elsif duration > 10
|
167
|
+
red "#{format_seconds(duration)}s"
|
168
|
+
elsif duration > 3
|
169
|
+
yellow "#{format_seconds(duration)}s"
|
170
|
+
else
|
171
|
+
"#{format_seconds(duration)}s"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def format_seconds(float)
|
176
|
+
precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION
|
177
|
+
sprintf("%.#{precision}f", float)
|
178
|
+
end
|
179
|
+
|
180
|
+
def color(text, color_code)
|
181
|
+
"#{color_code}#{text}\e[0m"
|
182
|
+
end
|
183
|
+
|
184
|
+
def bold(text)
|
185
|
+
color(text, "\e[1m")
|
186
|
+
end
|
187
|
+
|
188
|
+
def red(text)
|
189
|
+
color(text, "\e[31m")
|
190
|
+
end
|
191
|
+
|
192
|
+
def green(text)
|
193
|
+
color(text, "\e[32m")
|
194
|
+
end
|
195
|
+
|
196
|
+
def yellow(text)
|
197
|
+
color(text, "\e[33m")
|
198
|
+
end
|
199
|
+
|
200
|
+
def blue(text)
|
201
|
+
color(text, "\e[34m")
|
202
|
+
end
|
203
|
+
|
204
|
+
def magenta(text)
|
205
|
+
color(text, "\e[35m")
|
206
|
+
end
|
207
|
+
|
208
|
+
def cyan(text)
|
209
|
+
color(text, "\e[36m")
|
210
|
+
end
|
211
|
+
|
212
|
+
def white(text)
|
213
|
+
color(text, "\e[37m")
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cucumber-profiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Blumberg
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cucumber
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.2.1
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.1
|
30
|
+
description: A way to profile the performance of your cucumber features
|
31
|
+
email:
|
32
|
+
- mblum14@gmail.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.textile
|
40
|
+
- Rakefile
|
41
|
+
- cucumber-profiler.gemspec
|
42
|
+
- lib/cucumber-profiler.rb
|
43
|
+
- lib/cucumber/formatter/profiler.rb
|
44
|
+
homepage: https://github.com/mblum14/cucumber-profiler
|
45
|
+
licenses: []
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project: cucumber-profiler
|
64
|
+
rubygems_version: 1.8.24
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: A way to profile the performance of your cucumber features
|
68
|
+
test_files: []
|