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.
- checksums.yaml +4 -4
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +69 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +2 -16
- data/Rakefile +8 -0
- data/bin/setup +8 -0
- data/circle.yml +11 -0
- data/guides/any_fixture.md +1 -1
- data/guides/ruby_prof.md +0 -2
- data/guides/stack_prof.md +1 -5
- data/lib/test_prof.rb +6 -31
- data/lib/test_prof/event_prof.rb +4 -2
- data/lib/test_prof/event_prof/custom_events.rb +3 -3
- data/lib/test_prof/event_prof/custom_events/factory_create.rb +9 -11
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +9 -11
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +11 -13
- data/lib/test_prof/event_prof/rspec.rb +1 -5
- data/lib/test_prof/factory_doctor.rb +9 -11
- data/lib/test_prof/factory_doctor/rspec.rb +3 -5
- data/lib/test_prof/ruby_prof.rb +12 -6
- data/lib/test_prof/stack_prof.rb +7 -14
- data/lib/test_prof/version.rb +1 -1
- data/spec/integrations/any_fixture_spec.rb +11 -0
- data/spec/integrations/before_all_spec.rb +11 -0
- data/spec/integrations/event_prof_spec.rb +100 -0
- data/spec/integrations/factory_doctor_spec.rb +20 -0
- data/spec/integrations/fixtures/rspec/any_fixture_fixture.rb +37 -0
- data/spec/integrations/fixtures/rspec/before_all_fixture.rb +32 -0
- data/spec/integrations/fixtures/rspec/event_prof_factory_create_fixture.rb +23 -0
- data/spec/integrations/fixtures/rspec/event_prof_fixture.rb +51 -0
- data/spec/integrations/fixtures/rspec/event_prof_sidekiq_fixture.rb +54 -0
- data/spec/integrations/fixtures/rspec/factory_doctor_fixture.rb +33 -0
- data/spec/spec_helper.rb +38 -0
- data/spec/support/ar_models.rb +43 -0
- data/spec/support/instrumenter_stub.rb +19 -0
- data/spec/support/integration_helpers.rb +13 -0
- data/spec/support/transactional_context.rb +11 -0
- data/spec/test_prof/any_fixture_spec.rb +66 -0
- data/spec/test_prof/event_prof_spec.rb +138 -0
- data/spec/test_prof/ext/float_duration_spec.rb +12 -0
- data/spec/test_prof/factory_doctor_spec.rb +84 -0
- data/spec/test_prof/ruby_prof_spec.rb +109 -0
- data/spec/test_prof/stack_prof_spec.rb +73 -0
- data/spec/test_prof_spec.rb +23 -0
- data/test-prof.gemspec +35 -0
- metadata +34 -49
- data/CHANGELOG.md +0 -7
- data/assets/flamegraph.demo.html +0 -173
- data/assets/flamegraph.template.html +0 -196
- data/assets/src/d3-tip.js +0 -352
- data/assets/src/d3-tip.min.js +0 -1
- data/assets/src/d3.flameGraph.css +0 -92
- data/assets/src/d3.flameGraph.js +0 -459
- data/assets/src/d3.flameGraph.min.css +0 -1
- data/assets/src/d3.flameGraph.min.js +0 -1
- data/assets/src/d3.v4.min.js +0 -8
- data/guides/factory_default.md +0 -109
- data/guides/factory_prof.md +0 -85
- data/guides/rspec_stamp.md +0 -53
- data/guides/rubocop.md +0 -48
- data/guides/tag_prof.md +0 -52
- data/guides/tests_sampling.md +0 -24
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -140
- data/lib/test_prof/factory_default.rb +0 -58
- data/lib/test_prof/factory_default/factory_girl_patch.rb +0 -22
- data/lib/test_prof/factory_prof.rb +0 -140
- data/lib/test_prof/factory_prof/factory_girl_patch.rb +0 -12
- data/lib/test_prof/factory_prof/printers/flamegraph.rb +0 -71
- data/lib/test_prof/factory_prof/printers/simple.rb +0 -28
- data/lib/test_prof/recipes/minitest/sample.rb +0 -29
- data/lib/test_prof/recipes/rspec/factory_default.rb +0 -9
- data/lib/test_prof/recipes/rspec/sample.rb +0 -13
- data/lib/test_prof/rspec_stamp.rb +0 -135
- data/lib/test_prof/rspec_stamp/parser.rb +0 -103
- data/lib/test_prof/rspec_stamp/rspec.rb +0 -95
- data/lib/test_prof/rubocop.rb +0 -3
- data/lib/test_prof/tag_prof.rb +0 -8
- 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,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,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)
|