test-prof 0.1.0.beta4 → 0.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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)