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.
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
@@ -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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
@@ -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: []