test-prof 0.1.0.beta1 → 0.1.0.pre5

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +69 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +4 -0
  7. data/README.md +2 -6
  8. data/Rakefile +8 -0
  9. data/bin/setup +8 -0
  10. data/circle.yml +11 -0
  11. data/guides/any_fixture.md +1 -1
  12. data/guides/stack_prof.md +1 -1
  13. data/lib/test_prof/factory_doctor.rb +9 -11
  14. data/lib/test_prof/ruby_prof.rb +5 -2
  15. data/lib/test_prof/stack_prof.rb +5 -2
  16. data/lib/test_prof/version.rb +1 -1
  17. data/lib/test_prof.rb +1 -19
  18. data/spec/integrations/any_fixture_spec.rb +11 -0
  19. data/spec/integrations/before_all_spec.rb +11 -0
  20. data/spec/integrations/event_prof_spec.rb +100 -0
  21. data/spec/integrations/factory_doctor_spec.rb +20 -0
  22. data/spec/integrations/fixtures/rspec/any_fixture_fixture.rb +37 -0
  23. data/spec/integrations/fixtures/rspec/before_all_fixture.rb +32 -0
  24. data/spec/integrations/fixtures/rspec/event_prof_factory_create_fixture.rb +23 -0
  25. data/spec/integrations/fixtures/rspec/event_prof_fixture.rb +51 -0
  26. data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +53 -0
  27. data/spec/integrations/fixtures/rspec/factory_doctor_fixture.rb +33 -0
  28. data/spec/integrations/fixtures/rspec/rspec_stamp_fixture_tmpl.rb +33 -0
  29. data/spec/integrations/rspec_stamp_spec.rb +53 -0
  30. data/spec/spec_helper.rb +38 -0
  31. data/spec/support/ar_models.rb +43 -0
  32. data/spec/support/instrumenter_stub.rb +19 -0
  33. data/spec/support/integration_helpers.rb +13 -0
  34. data/spec/support/transactional_context.rb +11 -0
  35. data/spec/test_prof/any_fixture_spec.rb +66 -0
  36. data/spec/test_prof/event_prof_spec.rb +138 -0
  37. data/spec/test_prof/ext/float_duration_spec.rb +12 -0
  38. data/spec/test_prof/factory_doctor_spec.rb +84 -0
  39. data/spec/test_prof/rspec_stamp/parser_spec.rb +58 -0
  40. data/spec/test_prof/rspec_stamp_spec.rb +281 -0
  41. data/spec/test_prof/ruby_prof_spec.rb +109 -0
  42. data/spec/test_prof/stack_prof_spec.rb +73 -0
  43. data/spec/test_prof_spec.rb +23 -0
  44. data/test-prof.gemspec +35 -0
  45. metadata +38 -21
  46. data/CHANGELOG.md +0 -7
  47. data/assets/flamegraph.demo.html +0 -173
  48. data/assets/flamegraph.template.html +0 -196
  49. data/assets/src/d3-tip.js +0 -352
  50. data/assets/src/d3-tip.min.js +0 -1
  51. data/assets/src/d3.flameGraph.css +0 -92
  52. data/assets/src/d3.flameGraph.js +0 -459
  53. data/assets/src/d3.flameGraph.min.css +0 -1
  54. data/assets/src/d3.flameGraph.min.js +0 -1
  55. data/assets/src/d3.v4.min.js +0 -8
  56. data/guides/factory_prof.md +0 -85
  57. data/guides/rubocop.md +0 -48
  58. data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -140
  59. data/lib/test_prof/factory_prof/factory_girl_patch.rb +0 -12
  60. data/lib/test_prof/factory_prof/printers/flamegraph.rb +0 -71
  61. data/lib/test_prof/factory_prof/printers/simple.rb +0 -28
  62. data/lib/test_prof/factory_prof.rb +0 -140
  63. data/lib/test_prof/rubocop.rb +0 -3
@@ -1,85 +0,0 @@
1
- # FactoryProf
2
-
3
- FactoryProf tracks your factories usage statistics, i.e. how often each factory has been used.
4
-
5
- Example output:
6
-
7
- ```sh
8
- [TEST PROF INFO] Factories usage
9
-
10
- total top-level name
11
-
12
- 8 4 user
13
- 5 3 post
14
- ```
15
-
16
-
17
- It shows both the total number of the factory runs and the number of _top-level_ runs, i.e. not during another factory invocation (e.g. when using associations.)
18
-
19
- **NOTE**: FactoryProf only tracks the usage of `create` strategy.
20
-
21
- ## Instructions
22
-
23
- FactoryProf can only be used with FactoryGirl.
24
-
25
- To activate FactoryProf use `FPROF` environment variable:
26
-
27
- ```sh
28
- # Simple profiler
29
- FPROF=1 rspec
30
-
31
- # or
32
- FPROF=1 bundle exec rake test
33
- ```
34
-
35
- ## Factory Flamegraph
36
-
37
- The most useful feature of FactoryProf is the _FactoryFlame_ report. That's the special interpetation of Brendan Gregg's [flame graphs](http://www.brendangregg.com/flamegraphs.html) which allows you to identify _factory cascades_.
38
-
39
- To generate FactoryFlame report set `FPROF` environment variable to `flamegraph`:
40
-
41
- ```sh
42
- FPROF=flamegraph rspec
43
-
44
- # or
45
- FPROF=flamegraph bundle exec rake test
46
- ```
47
-
48
- That's how a report looks like:
49
-
50
- ![](https://s3-us-west-1.amazonaws.com/vladem/test-prof/factory-flame.gif)
51
-
52
- How to read this?
53
-
54
- Every column represents a _factory stack_ or _cascade_, that is a sequence of recursive `#create` method calls. Consider and example:
55
-
56
- ```ruby
57
- factory :comment do
58
- answer
59
- author
60
- end
61
-
62
- factory :answer do
63
- question
64
- author
65
- end
66
-
67
- factory :question do
68
- author
69
- end
70
-
71
- create(:comment) #=> creates 5 records
72
-
73
- # And the corresponding stack is:
74
- [:comment, :answer, :question, :author, :author, :author]
75
- ```
76
-
77
- The wider column the more often this stack appears.
78
-
79
- The `root` cell shows the total number of `create` calls.
80
-
81
- ## Acknowledgements
82
-
83
- - Thanks to [Martin Spier](https://github.com/spiermar) for [d3-flame-graph](https://github.com/spiermar/d3-flame-graph)
84
-
85
- - Thanks to [Sam Saffron](https://github.com/SamSaffron) for his [flame graphs implementation](https://github.com/SamSaffron/flamegraph).
data/guides/rubocop.md DELETED
@@ -1,48 +0,0 @@
1
- # Custom Rubocop Cops
2
-
3
- TestProf comes with the [Rubocop](https://github.com/bbatsov/rubocop) cops that help you write more performant tests.
4
-
5
- To enable them:
6
-
7
- - Require `test_prof/rubocop` in your Rubocop configuration:
8
-
9
- ```yml
10
- # .rubocop.yml
11
- require:
12
- - 'test_prof/rubocop'
13
- ```
14
-
15
- - Enable cops:
16
-
17
- ```yml
18
- RSpec/AggregateFailures:
19
- Enabled: true
20
- Include:
21
- - 'spec/**/*.rb'
22
- ```
23
-
24
- ## Rspec/AggregateFailures
25
-
26
- This cop encourages you to use one of the greatest features of the recent RSpec – aggregating failures within an example.
27
-
28
- Instead of writing one example per assertion, you can group _independent_ assertions together, thus running all setup hooks only once. That can dramatically increase your performance (by reducing the total number of examples).
29
-
30
- Consider an example:
31
-
32
- ```ruby
33
- # bad
34
- it { is_expected.to be_success }
35
- it { is_expected.to have_header('X-TOTAL-PAGES', 10) }
36
- it { is_expected.to have_header('X-NEXT-PAGE', 2) }
37
-
38
- # good
39
- it "returns the second page", :aggregate_failures do
40
- is_expected.to be_success
41
- is_expected.to have_header('X-TOTAL-PAGES', 10)
42
- is_expected.to have_header('X-NEXT-PAGE', 2)
43
- end
44
- ```
45
-
46
- This cop supports auto-correct feature, so you can automatically refactor you legacy tests!
47
-
48
- **NOTE**: auto-correction may break your tests (especially the ones using block-matchers, such as `change`).
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rubocop'
4
-
5
- module RuboCop
6
- module Cop
7
- module RSpec
8
- # Rejects and auto-corrects the usage of one-liners examples in favour of
9
- # :aggregate_failures feature.
10
- #
11
- # Example:
12
- #
13
- # # bad
14
- # it { is_expected.to be_success }
15
- # it { is_expected.to have_header('X-TOTAL-PAGES', 10) }
16
- # it { is_expected.to have_header('X-NEXT-PAGE', 2) }
17
- #
18
- # # good
19
- # it "returns the second page", :aggregate_failures do
20
- # is_expected.to be_success
21
- # is_expected.to have_header('X-TOTAL-PAGES', 10)
22
- # is_expected.to have_header('X-NEXT-PAGE', 2)
23
- # end
24
- #
25
- class AggregateFailures < RuboCop::Cop::Cop
26
- # From https://github.com/backus/rubocop-rspec/blob/master/lib/rubocop/rspec/language.rb
27
- GROUP_BLOCKS = %i[
28
- describe context feature example_group
29
- xdescribe xcontext xfeature
30
- fdescribe fcontext ffeature
31
- ].freeze
32
-
33
- EXAMPLE_BLOCKS = %i[
34
- it specify example scenario its
35
- fit fspecify fexample fscenario focus
36
- xit xspecify xexample xscenario ski
37
- pending
38
- ].freeze
39
-
40
- def on_block(node)
41
- method, _args, body = *node
42
- return unless body&.begin_type?
43
-
44
- _receiver, method_name, _object = *method
45
- return unless GROUP_BLOCKS.include?(method_name)
46
-
47
- return if check_node(body)
48
-
49
- add_offense(
50
- node,
51
- :expression,
52
- 'Use :aggregate_failures instead of several one-liners.'
53
- )
54
- end
55
-
56
- def autocorrect(node)
57
- _method, _args, body = *node
58
- iter = body.children.each
59
-
60
- first_example = loop do
61
- child = iter.next
62
- break child if oneliner?(child)
63
- end
64
-
65
- base_indent = " " * first_example.source_range.column
66
-
67
- replacements = [
68
- header_from(first_example),
69
- body_from(first_example, base_indent)
70
- ]
71
-
72
- last_example = nil
73
-
74
- loop do
75
- child = iter.next
76
- break unless oneliner?(child)
77
- last_example = child
78
- replacements << body_from(child, base_indent)
79
- end
80
-
81
- replacements << "#{base_indent}end"
82
-
83
- range = first_example.source_range.begin.join(
84
- last_example.source_range.end
85
- )
86
-
87
- replacement = replacements.join("\n")
88
-
89
- lambda do |corrector|
90
- corrector.replace(range, replacement)
91
- end
92
- end
93
-
94
- private
95
-
96
- def check_node(node)
97
- offenders = 0
98
-
99
- node.children.each do |child|
100
- if oneliner?(child)
101
- offenders += 1
102
- elsif example_node?(child)
103
- break if offenders > 1
104
- offenders = 0
105
- end
106
- end
107
-
108
- offenders < 2
109
- end
110
-
111
- def oneliner?(node)
112
- node&.block_type? &&
113
- (node.source.lines.size == 1) &&
114
- example_node?(node)
115
- end
116
-
117
- def example_node?(node)
118
- method, _args, _body = *node
119
- _receiver, method_name, _object = *method
120
- EXAMPLE_BLOCKS.include?(method_name)
121
- end
122
-
123
- def header_from(node)
124
- method, _args, _body = *node
125
- _receiver, method_name, _object = *method
126
- %(#{method_name} "works", :aggregate_failures do)
127
- end
128
-
129
- def body_from(node, base_indent = '')
130
- _method, _args, body = *node
131
- "#{base_indent}#{indent}#{body.source}"
132
- end
133
-
134
- def indent
135
- @indent ||= " " * (config.for_cop('IndentationWidth')['Width'] || 2)
136
- end
137
- end
138
- end
139
- end
140
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestProf
4
- module FactoryProf
5
- # Wrap #run method with FactoryProf tracking
6
- module FactoryGirlPatch
7
- def run(strategy = @strategy)
8
- FactoryProf.track(strategy, @name) { super }
9
- end
10
- end
11
- end
12
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "json"
4
-
5
- module TestProf::FactoryProf
6
- module Printers
7
- module Flamegraph # :nodoc: all
8
- class << self
9
- include TestProf::Logging
10
-
11
- def dump(result)
12
- report_data = {
13
- total_stacks: result.stacks.size,
14
- total: result.total
15
- }
16
-
17
- report_data[:roots] = convert_stacks(result)
18
-
19
- path = generate_html(report_data)
20
-
21
- log :info, "FactoryFlame report generated: #{path}"
22
- end
23
-
24
- def convert_stacks(result)
25
- res = []
26
-
27
- paths = {}
28
-
29
- result.stacks.each do |stack|
30
- parent = nil
31
- path = ""
32
-
33
- stack.each do |sample|
34
- path = "#{path}/#{sample}"
35
-
36
- if paths[path]
37
- node = paths[path]
38
- node[:value] += 1
39
- else
40
- node = { name: sample, value: 1, total: result.raw_stats.fetch(sample)[:total] }
41
- paths[path] = node
42
-
43
- if parent.nil?
44
- res << node
45
- else
46
- parent[:children] ||= []
47
- parent[:children] << node
48
- end
49
- end
50
-
51
- parent = node
52
- end
53
- end
54
-
55
- res
56
- end
57
-
58
- private
59
-
60
- def generate_html(data)
61
- template = File.read(TestProf.asset_path("flamegraph.template.html"))
62
- template.sub! '/**REPORT-DATA**/', data.to_json
63
-
64
- outpath = TestProf.artefact_path("factory-flame.html")
65
- File.write(outpath, template)
66
- outpath
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestProf::FactoryProf
4
- module Printers
5
- module Simple # :nodoc: all
6
- class << self
7
- include TestProf::Logging
8
-
9
- def dump(result)
10
- msgs = []
11
-
12
- msgs <<
13
- <<~MSG
14
- Factories usage
15
-
16
- total top-level name
17
- MSG
18
-
19
- result.stats.each do |stat|
20
- msgs << format("%6d %11d %15s", stat[:total], stat[:top_level], stat[:name])
21
- end
22
-
23
- log :info, msgs.join("\n")
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_prof/factory_prof/factory_girl_patch"
4
- require "test_prof/factory_prof/printers/simple"
5
- require "test_prof/factory_prof/printers/flamegraph"
6
-
7
- module TestProf
8
- # FactoryProf collects "factory stacks" that can be used to build
9
- # flamegraphs or detect most popular factories
10
- module FactoryProf
11
- # FactoryProf configuration
12
- class Configuration
13
- attr_accessor :mode
14
-
15
- def initialize
16
- @mode = ENV['FPROF'] == 'flamegraph' ? :flamegraph : :simple
17
- end
18
-
19
- # Whether we want to generate flamegraphs
20
- def flamegraph?
21
- @mode == :flamegraph
22
- end
23
- end
24
-
25
- class Result # :nodoc:
26
- attr_reader :stacks, :raw_stats
27
-
28
- def initialize(stacks, raw_stats)
29
- @stacks = stacks
30
- @raw_stats = raw_stats
31
- end
32
-
33
- # Returns sorted stats
34
- def stats
35
- return @stats if instance_variable_defined?(:@stats)
36
-
37
- @stats = @raw_stats.values
38
- .sort_by { |el| -el[:total] }
39
- end
40
-
41
- def total
42
- return @total if instance_variable_defined?(:@total)
43
- @total = @raw_stats.values.sum { |v| v[:total] }
44
- end
45
-
46
- private
47
-
48
- def sorted_stats(key)
49
- @raw_stats.values
50
- .map { |el| [el[:name], el[key]] }
51
- .sort_by { |el| -el[1] }
52
- end
53
- end
54
-
55
- class << self
56
- include TestProf::Logging
57
-
58
- def config
59
- @config ||= Configuration.new
60
- end
61
-
62
- def configure
63
- yield config
64
- end
65
-
66
- # Patch factory lib, init vars
67
- def init
68
- @running = false
69
-
70
- log :info, "FactoryProf enabled (#{config.mode} mode)"
71
-
72
- # Monkey-patch FactoryGirl
73
- ::FactoryGirl::FactoryRunner.prepend(FactoryGirlPatch) if
74
- defined?(::FactoryGirl)
75
- end
76
-
77
- # Inits FactoryProf and setups at exit hook,
78
- # then runs
79
- def run
80
- init
81
-
82
- printer = config.flamegraph? ? Printers::Flamegraph : Printers::Simple
83
-
84
- at_exit { printer.dump(result) }
85
-
86
- start
87
- end
88
-
89
- def start
90
- reset!
91
- @running = true
92
- end
93
-
94
- def stop
95
- @running = false
96
- end
97
-
98
- def result
99
- Result.new(@stacks, @stats)
100
- end
101
-
102
- def track(strategy, factory)
103
- return yield if !running? || (strategy != :create)
104
- begin
105
- @depth += 1
106
- @current_stack << factory if config.flamegraph?
107
- @stats[factory][:total] += 1
108
- @stats[factory][:top_level] += 1 if @depth == 1
109
- yield
110
- ensure
111
- @depth -= 1
112
- flush_stack if @depth.zero?
113
- end
114
- end
115
-
116
- private
117
-
118
- def reset!
119
- @stacks = [] if config.flamegraph?
120
- @depth = 0
121
- @stats = Hash.new { |h, k| h[k] = { name: k, total: 0, top_level: 0 } }
122
- flush_stack
123
- end
124
-
125
- def flush_stack
126
- return unless config.flamegraph?
127
- @stacks << @current_stack unless @current_stack.nil? || @current_stack.empty?
128
- @current_stack = []
129
- end
130
-
131
- def running?
132
- @running == true
133
- end
134
- end
135
- end
136
- end
137
-
138
- TestProf.activate('FPROF') do
139
- TestProf::FactoryProf.run
140
- end
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_prof/cops/rspec/aggregate_failures"