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.
- 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)
|