test-prof 0.1.0.beta4 → 0.1.0.pre2

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 (81) 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 -16
  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/ruby_prof.md +0 -2
  13. data/guides/stack_prof.md +1 -5
  14. data/lib/test_prof.rb +6 -31
  15. data/lib/test_prof/event_prof.rb +4 -2
  16. data/lib/test_prof/event_prof/custom_events.rb +3 -3
  17. data/lib/test_prof/event_prof/custom_events/factory_create.rb +9 -11
  18. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +9 -11
  19. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +11 -13
  20. data/lib/test_prof/event_prof/rspec.rb +1 -5
  21. data/lib/test_prof/factory_doctor.rb +9 -11
  22. data/lib/test_prof/factory_doctor/rspec.rb +3 -5
  23. data/lib/test_prof/ruby_prof.rb +12 -6
  24. data/lib/test_prof/stack_prof.rb +7 -14
  25. data/lib/test_prof/version.rb +1 -1
  26. data/spec/integrations/any_fixture_spec.rb +11 -0
  27. data/spec/integrations/before_all_spec.rb +11 -0
  28. data/spec/integrations/event_prof_spec.rb +100 -0
  29. data/spec/integrations/factory_doctor_spec.rb +20 -0
  30. data/spec/integrations/fixtures/rspec/any_fixture_fixture.rb +37 -0
  31. data/spec/integrations/fixtures/rspec/before_all_fixture.rb +32 -0
  32. data/spec/integrations/fixtures/rspec/event_prof_factory_create_fixture.rb +23 -0
  33. data/spec/integrations/fixtures/rspec/event_prof_fixture.rb +51 -0
  34. data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +54 -0
  35. data/spec/integrations/fixtures/rspec/factory_doctor_fixture.rb +33 -0
  36. data/spec/spec_helper.rb +38 -0
  37. data/spec/support/ar_models.rb +43 -0
  38. data/spec/support/instrumenter_stub.rb +19 -0
  39. data/spec/support/integration_helpers.rb +13 -0
  40. data/spec/support/transactional_context.rb +11 -0
  41. data/spec/test_prof/any_fixture_spec.rb +66 -0
  42. data/spec/test_prof/event_prof_spec.rb +138 -0
  43. data/spec/test_prof/ext/float_duration_spec.rb +12 -0
  44. data/spec/test_prof/factory_doctor_spec.rb +84 -0
  45. data/spec/test_prof/ruby_prof_spec.rb +109 -0
  46. data/spec/test_prof/stack_prof_spec.rb +73 -0
  47. data/spec/test_prof_spec.rb +23 -0
  48. data/test-prof.gemspec +35 -0
  49. metadata +34 -49
  50. data/CHANGELOG.md +0 -7
  51. data/assets/flamegraph.demo.html +0 -173
  52. data/assets/flamegraph.template.html +0 -196
  53. data/assets/src/d3-tip.js +0 -352
  54. data/assets/src/d3-tip.min.js +0 -1
  55. data/assets/src/d3.flameGraph.css +0 -92
  56. data/assets/src/d3.flameGraph.js +0 -459
  57. data/assets/src/d3.flameGraph.min.css +0 -1
  58. data/assets/src/d3.flameGraph.min.js +0 -1
  59. data/assets/src/d3.v4.min.js +0 -8
  60. data/guides/factory_default.md +0 -109
  61. data/guides/factory_prof.md +0 -85
  62. data/guides/rspec_stamp.md +0 -53
  63. data/guides/rubocop.md +0 -48
  64. data/guides/tag_prof.md +0 -52
  65. data/guides/tests_sampling.md +0 -24
  66. data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -140
  67. data/lib/test_prof/factory_default.rb +0 -58
  68. data/lib/test_prof/factory_default/factory_girl_patch.rb +0 -22
  69. data/lib/test_prof/factory_prof.rb +0 -140
  70. data/lib/test_prof/factory_prof/factory_girl_patch.rb +0 -12
  71. data/lib/test_prof/factory_prof/printers/flamegraph.rb +0 -71
  72. data/lib/test_prof/factory_prof/printers/simple.rb +0 -28
  73. data/lib/test_prof/recipes/minitest/sample.rb +0 -29
  74. data/lib/test_prof/recipes/rspec/factory_default.rb +0 -9
  75. data/lib/test_prof/recipes/rspec/sample.rb +0 -13
  76. data/lib/test_prof/rspec_stamp.rb +0 -135
  77. data/lib/test_prof/rspec_stamp/parser.rb +0 -103
  78. data/lib/test_prof/rspec_stamp/rspec.rb +0 -95
  79. data/lib/test_prof/rubocop.rb +0 -3
  80. data/lib/test_prof/tag_prof.rb +0 -8
  81. data/lib/test_prof/tag_prof/rspec.rb +0 -84
@@ -1,58 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_prof/factory_default/factory_girl_patch"
4
-
5
- module TestProf
6
- # FactoryDefault allows use to re-use associated objects
7
- # in factories implicilty
8
- module FactoryDefault
9
- module DefaultSyntax # :nodoc:
10
- def create_default(name, *args, &block)
11
- set_factory_default(
12
- name,
13
- FactoryGirl.create(name, *args, &block)
14
- )
15
- end
16
-
17
- def set_factory_default(name, obj)
18
- FactoryDefault.register(name, obj)
19
- end
20
- end
21
-
22
- class << self
23
- def init
24
- FactoryGirl::Syntax::Methods.include DefaultSyntax
25
- FactoryGirl.extend DefaultSyntax
26
- FactoryGirl::Strategy::Create.prepend StrategyExt
27
- FactoryGirl::Strategy::Build.prepend StrategyExt
28
- FactoryGirl::Strategy::Stub.prepend StrategyExt
29
-
30
- @store = {}
31
- end
32
-
33
- def register(name, obj)
34
- store[name] = obj
35
- end
36
-
37
- def get(name)
38
- store[name]
39
- end
40
-
41
- def exists?(name)
42
- store.key?(name)
43
- end
44
-
45
- def remove(name)
46
- store.delete(name)
47
- end
48
-
49
- def reset
50
- @store.clear
51
- end
52
-
53
- private
54
-
55
- attr_reader :store
56
- end
57
- end
58
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestProf
4
- module FactoryDefault # :nodoc: all
5
- module RunnerExt
6
- refine FactoryGirl::FactoryRunner do
7
- def name
8
- @name
9
- end
10
- end
11
- end
12
-
13
- using RunnerExt
14
-
15
- module StrategyExt
16
- def association(runner)
17
- return super unless FactoryDefault.exists?(runner.name)
18
- FactoryDefault.get(runner.name)
19
- end
20
- end
21
- end
22
- 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,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 %30s", 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,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestProf
4
- # Add ability to run only a specified number of example groups (randomly selected)
5
- module MinitestSample
6
- # Do not add these classes to resulted sample
7
- CORE_RUNNABLES = [
8
- Minitest::Test,
9
- Minitest::Unit::TestCase,
10
- Minitest::Spec
11
- ].freeze
12
-
13
- def run(*)
14
- unless ENV['SAMPLE'].nil?
15
- sample_size = ENV['SAMPLE'].to_i
16
- # Make sure that sample contains only _real_ suites
17
- runnables = Minitest::Runnable.runnables
18
- .sample(sample_size + CORE_RUNNABLES.size)
19
- .reject { |suite| CORE_RUNNABLES.include?(suite) }
20
- .take(sample_size)
21
- Minitest::Runnable.reset
22
- runnables.each { |r| Minitest::Runnable.runnables << r }
23
- end
24
- super
25
- end
26
- end
27
- end
28
-
29
- Minitest.singleton_class.prepend(TestProf::MinitestSample)
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_prof/factory_default"
4
-
5
- TestProf::FactoryDefault.init
6
-
7
- RSpec.configure do |config|
8
- config.after(:each) { TestProf::FactoryDefault.reset }
9
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestProf
4
- # Add ability to run only a specified number of example groups (randomly selected)
5
- module RspecSample
6
- def ordered_example_groups
7
- @example_groups = @example_groups.sample(ENV['SAMPLE'].to_i) unless ENV['SAMPLE'].nil?
8
- super
9
- end
10
- end
11
- end
12
-
13
- RSpec::Core::World.prepend(TestProf::RspecSample)
@@ -1,135 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_prof/logging"
4
- require "test_prof/rspec_stamp/parser"
5
-
6
- module TestProf
7
- # Mark RSpec examples with provided tags
8
- module RSpecStamp
9
- EXAMPLE_RXP = /(\s*)(\w+\s*(?:.*)\s*)(do|{)/
10
-
11
- # RSpecStamp configuration
12
- class Configuration
13
- attr_accessor :ignore_files, :dry_run, :tags
14
-
15
- def initialize
16
- @ignore_files = [%r{spec/support}]
17
- @dry_run = ENV['RSTAMP_DRY_RUN'] == '1'
18
- self.tags = ENV['RSTAMP']
19
- end
20
-
21
- def dry_run?
22
- @dry_run == true
23
- end
24
-
25
- def tags=(val)
26
- @tags = if val.is_a?(String)
27
- parse_tags(val)
28
- else
29
- val
30
- end
31
- end
32
-
33
- private
34
-
35
- def parse_tags(str)
36
- str.split(/\s*,\s*/).each_with_object([]) do |tag, acc|
37
- k, v = tag.split(":")
38
- acc << if v.nil?
39
- k.to_sym
40
- else
41
- Hash[k.to_sym, v.to_sym]
42
- end
43
- end
44
- end
45
- end
46
-
47
- class << self
48
- include TestProf::Logging
49
-
50
- def config
51
- @config ||= Configuration.new
52
- end
53
-
54
- def configure
55
- yield config
56
- end
57
-
58
- # Accepts source code (as array of lines),
59
- # line numbers (of example to apply tags)
60
- # and an array of tags.
61
- def apply_tags(code, lines, tags)
62
- failed = 0
63
-
64
- lines.each do |line|
65
- unless stamp_example(code[line - 1], tags)
66
- failed += 1
67
- log :warn, "Failed to stamp: #{code[line - 1]}"
68
- end
69
- end
70
- failed
71
- end
72
-
73
- private
74
-
75
- # rubocop: disable Metrics/CyclomaticComplexity
76
- # rubocop: disable Metrics/PerceivedComplexity
77
- def stamp_example(example, tags)
78
- matches = example.match(EXAMPLE_RXP)
79
- return false unless matches
80
-
81
- code = matches[2]
82
- block = matches[3]
83
-
84
- parsed = Parser.parse(code)
85
- return false unless parsed
86
-
87
- parsed.desc ||= 'works'
88
-
89
- tags.each do |t|
90
- if t.is_a?(Hash)
91
- t.keys.each { |k| parsed.add_htag(k, t[k]) }
92
- else
93
- parsed.add_tag(t)
94
- end
95
- end
96
-
97
- need_parens = block == "{"
98
-
99
- tags_str = parsed.tags.map { |t| t.is_a?(Symbol) ? ":#{t}" : t }.join(", ") unless
100
- parsed.tags.nil?
101
-
102
- unless parsed.htags.nil?
103
- htags_str = parsed.htags.map do |(k, v)|
104
- vstr = v.is_a?(Symbol) ? ":#{v}" : quote(v)
105
-
106
- "#{k}: #{vstr}"
107
- end
108
- end
109
-
110
- replacement = "\\1#{parsed.fname}#{need_parens ? '(' : ' '}"\
111
- "#{[quote(parsed.desc), tags_str, htags_str].compact.join(', ')}"\
112
- "#{need_parens ? ') ' : ' '}\\3"
113
-
114
- if config.dry_run?
115
- log :info, "Patched: #{example.sub(EXAMPLE_RXP, replacement)}"
116
- else
117
- example.sub!(EXAMPLE_RXP, replacement)
118
- end
119
- true
120
- end
121
- # rubocop: enable Metrics/CyclomaticComplexity
122
- # rubocop: enable Metrics/PerceivedComplexity
123
-
124
- def quote(str)
125
- if str.include?("'")
126
- "\"#{str}\""
127
- else
128
- "'#{str}'"
129
- end
130
- end
131
- end
132
- end
133
- end
134
-
135
- require "test_prof/rspec_stamp/rspec" if defined?(RSpec)