cucumber-profiler 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|