test-prof 1.5.0 → 1.6.0
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/CHANGELOG.md +24 -0
- data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
- data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
- data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples.rb +5 -5
- data/lib/{test_prof/cops → rubocop/test_prof}/plugin.rb +2 -2
- data/lib/rubocop/test_prof.rb +9 -0
- data/lib/test-prof.rb +5 -0
- data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
- data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
- data/lib/test_prof/memory_prof/printer.rb +31 -0
- data/lib/test_prof/memory_prof/rspec.rb +2 -0
- data/lib/test_prof/memory_prof/tracker.rb +11 -1
- data/lib/test_prof/memory_prof.rb +8 -3
- data/lib/test_prof/recipes/rspec/let_it_be.rb +1 -1
- data/lib/test_prof/rspec_dissect/collector.rb +58 -0
- data/lib/test_prof/rspec_dissect/rspec.rb +13 -21
- data/lib/test_prof/rspec_dissect.rb +45 -51
- data/lib/test_prof/rspec_stamp/parser/prism.rb +75 -0
- data/lib/test_prof/rspec_stamp/parser/ripper.rb +128 -0
- data/lib/test_prof/rspec_stamp/parser.rb +8 -120
- data/lib/test_prof/rspec_stamp.rb +1 -0
- data/lib/test_prof/rubocop.rb +10 -9
- data/lib/test_prof/tps_prof/profiler.rb +52 -15
- data/lib/test_prof/tps_prof/reporter/text.rb +27 -7
- data/lib/test_prof/tps_prof/rspec.rb +25 -2
- data/lib/test_prof/tps_prof.rb +69 -4
- data/lib/test_prof/version.rb +1 -1
- metadata +14 -13
- data/lib/test_prof/rspec_dissect/collectors/base.rb +0 -70
- data/lib/test_prof/rspec_dissect/collectors/before.rb +0 -19
- data/lib/test_prof/rspec_dissect/collectors/let.rb +0 -39
- /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/its.rb +0 -0
- /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/line_range_helpers.rb +0 -0
- /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/metadata_helpers.rb +0 -0
- /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/language.rb +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b843db2a99bf53e8c380e748bb73340c573e1de8082f76519530eb0ddf32cb95
|
|
4
|
+
data.tar.gz: 94048d78eaf4857b0a869fad4d551701b0501074cb97ff9ac9ad13a5b69eee03
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5718228b4b63106230f730ef7ee2e1ed53de8457c266f82859a2824b7f9967ba30d0d26d2ee9e4fb1f6363e04eb6a790560e98d529d7e630d93f7dd9dd2dc990
|
|
7
|
+
data.tar.gz: b6df0f87115a3cfd159b1a132459a7958c204374e9b76a8f0c7282158fe6fe6a11a208a5e60f111d98f89c212d215c8cf6db1087c071257a92a7377f999cae0c
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## master (unreleased)
|
|
4
4
|
|
|
5
|
+
## 1.6.0 (2026-03-18)
|
|
6
|
+
|
|
7
|
+
- Add TPS profiler. ([@palkan][])
|
|
8
|
+
|
|
9
|
+
- Add GC profiling to MemoryProf. ([@palkan][])
|
|
10
|
+
|
|
11
|
+
- Upgrade to Sidekiq 8. ([@palkan][])
|
|
12
|
+
|
|
13
|
+
- RSpecStamp now uses Prism if available. ([@kddnewton][])
|
|
14
|
+
|
|
15
|
+
- Upgrade RSpecDissect to show total setup time and let breakdowns. ([@palkan][])
|
|
16
|
+
|
|
17
|
+
## 1.5.2 (2026-02-03)
|
|
18
|
+
|
|
19
|
+
- Avoid using `Gem.loaded_specs` methods in RuboCop plugin version check. ([@Rylan12][])
|
|
20
|
+
|
|
21
|
+
## 1.5.1 (2026-01-27)
|
|
22
|
+
|
|
23
|
+
- Fix RuboCop plugin. ([@palkan][])
|
|
24
|
+
|
|
25
|
+
Now you should use `--plugin test-prof` or `plugins: [test-prof]` (in YAML) (so LintRoller can correctly obtain the plugin class name from the gemspec).
|
|
26
|
+
|
|
5
27
|
## 1.5.0 (2025-12-04)
|
|
6
28
|
|
|
7
29
|
- Logging: support Rails 8.2 structured events based logging. ([@palkan][])
|
|
@@ -478,3 +500,5 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md)
|
|
|
478
500
|
[@john-h-k]: https://github.com/john-h-k
|
|
479
501
|
[@devinburnette]: https://github.com/devinburnette
|
|
480
502
|
[@elasticspoon]: https://github.com/elasticspoon
|
|
503
|
+
[@Rylan12]: https://github.com/Rylan12
|
|
504
|
+
[@kddnewton]: https://github.com/kddnewton
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
require_relative "aggregate_examples/line_range_helpers"
|
|
4
|
+
require_relative "aggregate_examples/metadata_helpers"
|
|
5
|
+
require_relative "aggregate_examples/node_matchers"
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
require_relative "aggregate_examples/its"
|
|
8
|
+
require_relative "aggregate_examples/matchers_with_side_effects"
|
|
9
9
|
|
|
10
10
|
module RuboCop
|
|
11
11
|
module Cop
|
|
@@ -8,8 +8,8 @@ module RuboCop
|
|
|
8
8
|
class Plugin < LintRoller::Plugin
|
|
9
9
|
def about
|
|
10
10
|
LintRoller::About.new(
|
|
11
|
-
name: "
|
|
12
|
-
version: VERSION,
|
|
11
|
+
name: "test-prof",
|
|
12
|
+
version: ::TestProf::VERSION,
|
|
13
13
|
homepage: "https://test-prof.evilmartians.io/misc/rubocop",
|
|
14
14
|
description: "RuboCop plugin to help you write more performant tests."
|
|
15
15
|
)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
if Gem::Version.new(RuboCop::Version::STRING) < Gem::Version.new("0.51.0")
|
|
4
|
+
warn "TestProf cops require RuboCop >= 0.51.0 to run."
|
|
5
|
+
return
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
require "rubocop/test_prof/plugin"
|
|
9
|
+
require "rubocop/test_prof/cops/rspec/aggregate_examples"
|
data/lib/test-prof.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "test_prof/ext/float_duration"
|
|
3
4
|
require "test_prof/memory_prof/printer/number_to_human"
|
|
4
5
|
require "test_prof/ext/string_truncate"
|
|
5
6
|
|
|
@@ -92,5 +93,35 @@ module TestProf
|
|
|
92
93
|
number_to_human(item[:memory])
|
|
93
94
|
end
|
|
94
95
|
end
|
|
96
|
+
|
|
97
|
+
class GCPrinter < Printer
|
|
98
|
+
using StringTruncate
|
|
99
|
+
using FloatDuration
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def mode
|
|
104
|
+
"GC time"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def print_total
|
|
108
|
+
"Total GC time: #{(GC.total_time.to_f / 1_000_000_000).duration}\n\n"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def print_items(items)
|
|
112
|
+
messages =
|
|
113
|
+
items.map do |item|
|
|
114
|
+
<<~ITEM
|
|
115
|
+
#{item[:name].truncate(30)} (#{item[:location]}) – #{gc_time(item)}
|
|
116
|
+
ITEM
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
messages.join
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def gc_time(item)
|
|
123
|
+
(item[:memory].to_f / 1_000_000_000).duration
|
|
124
|
+
end
|
|
125
|
+
end
|
|
95
126
|
end
|
|
96
127
|
end
|
|
@@ -31,10 +31,12 @@ module TestProf
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def example_group_started(notification)
|
|
34
|
+
return unless notification.group.top_level?
|
|
34
35
|
tracker.group_started(notification.group, group(notification))
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
def example_group_finished(notification)
|
|
39
|
+
return unless notification.group.top_level?
|
|
38
40
|
tracker.group_finished(notification.group)
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -55,7 +55,7 @@ module TestProf
|
|
|
55
55
|
node = list.remove_node(id, track)
|
|
56
56
|
return unless node
|
|
57
57
|
|
|
58
|
-
groups << {**node.item, memory: node.
|
|
58
|
+
groups << {**node.item, memory: node.total_memory}
|
|
59
59
|
end
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -84,5 +84,15 @@ module TestProf
|
|
|
84
84
|
!!@rss_tool
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
|
+
|
|
88
|
+
class GCTracker < Tracker
|
|
89
|
+
def track
|
|
90
|
+
::GC.total_time
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def supported?
|
|
94
|
+
::GC.respond_to?(:total_time)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
87
97
|
end
|
|
88
98
|
end
|
|
@@ -11,6 +11,7 @@ module TestProf
|
|
|
11
11
|
#
|
|
12
12
|
# TEST_MEM_PROF='rss' rspec ...
|
|
13
13
|
# TEST_MEM_PROF='alloc' rspec ...
|
|
14
|
+
# TEST_MEM_PROF='gc' rspec ...
|
|
14
15
|
#
|
|
15
16
|
# By default MemoryProf shows the top 5 examples and groups (for RSpec) but you can
|
|
16
17
|
# set how many items to display with `TEST_MEM_PROF_COUNT`:
|
|
@@ -25,6 +26,8 @@ module TestProf
|
|
|
25
26
|
module MemoryProf
|
|
26
27
|
# MemoryProf configuration
|
|
27
28
|
class Configuration
|
|
29
|
+
MODES = %w[alloc rss gc].freeze
|
|
30
|
+
|
|
28
31
|
attr_reader :mode, :top_count
|
|
29
32
|
|
|
30
33
|
def initialize
|
|
@@ -33,7 +36,7 @@ module TestProf
|
|
|
33
36
|
end
|
|
34
37
|
|
|
35
38
|
def mode=(value)
|
|
36
|
-
@mode = (value
|
|
39
|
+
@mode = MODES.include?(value) ? value.to_sym : :rss
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
def top_count=(value)
|
|
@@ -45,12 +48,14 @@ module TestProf
|
|
|
45
48
|
class << self
|
|
46
49
|
TRACKERS = {
|
|
47
50
|
alloc: AllocTracker,
|
|
48
|
-
rss: RssTracker
|
|
51
|
+
rss: RssTracker,
|
|
52
|
+
gc: GCTracker
|
|
49
53
|
}.freeze
|
|
50
54
|
|
|
51
55
|
PRINTERS = {
|
|
52
56
|
alloc: AllocPrinter,
|
|
53
|
-
rss: RssPrinter
|
|
57
|
+
rss: RssPrinter,
|
|
58
|
+
gc: GCPrinter
|
|
54
59
|
}.freeze
|
|
55
60
|
|
|
56
61
|
def config
|
|
@@ -142,7 +142,7 @@ module TestProf
|
|
|
142
142
|
end
|
|
143
143
|
|
|
144
144
|
private def report_duplicates(identifier)
|
|
145
|
-
if
|
|
145
|
+
if method_defined?(identifier) && File.basename(__FILE__) == File.basename(instance_method(identifier).source_location[0])
|
|
146
146
|
error_msg = "let_it_be(:#{identifier}) was redefined in nested group"
|
|
147
147
|
report_level = LetItBe.config.report_duplicates.to_sym
|
|
148
148
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_prof/utils/sized_ordered_set"
|
|
4
|
+
require "test_prof/ext/float_duration"
|
|
5
|
+
require "test_prof/ext/string_truncate"
|
|
6
|
+
|
|
7
|
+
module TestProf # :nodoc: all
|
|
8
|
+
using FloatDuration
|
|
9
|
+
using StringTruncate
|
|
10
|
+
|
|
11
|
+
module RSpecDissect
|
|
12
|
+
class Collector
|
|
13
|
+
attr_reader :results, :name, :top_count
|
|
14
|
+
|
|
15
|
+
def initialize(top_count:)
|
|
16
|
+
@top_count = top_count
|
|
17
|
+
@results = Utils::SizedOrderedSet.new(
|
|
18
|
+
top_count, sort_by: :total_setup
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def <<(data)
|
|
23
|
+
results << data
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def print_result_header
|
|
27
|
+
<<~MSG
|
|
28
|
+
|
|
29
|
+
Top #{top_count} slowest suites by setup time:
|
|
30
|
+
|
|
31
|
+
MSG
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def print_group_result(group)
|
|
35
|
+
"#{group[:desc].truncate} (#{group[:loc]}) – \e[1m#{group[:total_setup].duration}\e[22m " \
|
|
36
|
+
"of #{group[:total].duration} / #{group[:count]} " \
|
|
37
|
+
"(before: #{(group[:total_setup] - group[:total_lazy_let]).duration}, " \
|
|
38
|
+
"before let: #{group[:total_before_let].duration}, " \
|
|
39
|
+
"lazy let: #{group[:total_lazy_let].duration})"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def print_results
|
|
43
|
+
msgs = [print_result_header]
|
|
44
|
+
|
|
45
|
+
results.each do |group|
|
|
46
|
+
msgs << print_group_result(group)
|
|
47
|
+
msgs << "\n" if group[:top_lets].any?
|
|
48
|
+
group[:top_lets].each do |let|
|
|
49
|
+
msgs << " ↳ #{let[:name]} – #{let[:duration].duration} (#{let[:size]})\n"
|
|
50
|
+
end
|
|
51
|
+
msgs << "\n"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
msgs.join
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "test_prof/rspec_dissect/collector"
|
|
4
|
+
|
|
3
5
|
require "test_prof/ext/float_duration"
|
|
4
6
|
|
|
5
7
|
module TestProf
|
|
@@ -15,19 +17,12 @@ module TestProf
|
|
|
15
17
|
].freeze
|
|
16
18
|
|
|
17
19
|
def initialize
|
|
18
|
-
@
|
|
19
|
-
|
|
20
|
-
if RSpecDissect.config.let?
|
|
21
|
-
collectors << Collectors::Let.new(top_count: RSpecDissect.config.top_count)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
if RSpecDissect.config.before?
|
|
25
|
-
collectors << Collectors::Before.new(top_count: RSpecDissect.config.top_count)
|
|
26
|
-
end
|
|
20
|
+
@collector = Collector.new(top_count: top_count)
|
|
27
21
|
|
|
28
22
|
@examples_count = 0
|
|
29
23
|
@examples_time = 0.0
|
|
30
24
|
@total_examples_time = 0.0
|
|
25
|
+
@total_setup_time = 0.0
|
|
31
26
|
end
|
|
32
27
|
|
|
33
28
|
def example_finished(notification)
|
|
@@ -44,9 +39,11 @@ module TestProf
|
|
|
44
39
|
data[:desc] = notification.group.top_level_description
|
|
45
40
|
data[:loc] = notification.group.metadata[:location]
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
collectors.each { |c| c << data }
|
|
42
|
+
RSpecDissect.populate_from_spans!(data)
|
|
49
43
|
|
|
44
|
+
collector << data
|
|
45
|
+
|
|
46
|
+
@total_setup_time += data[:total_setup]
|
|
50
47
|
@total_examples_time += @examples_time
|
|
51
48
|
@examples_count = 0
|
|
52
49
|
@examples_time = 0.0
|
|
@@ -62,17 +59,12 @@ module TestProf
|
|
|
62
59
|
RSpecDissect report
|
|
63
60
|
|
|
64
61
|
Total time: #{@total_examples_time.duration}
|
|
62
|
+
Total setup time: #{@total_setup_time.duration}
|
|
65
63
|
MSG
|
|
66
64
|
|
|
67
|
-
collectors.each do |c|
|
|
68
|
-
msgs << c.total_time_message
|
|
69
|
-
end
|
|
70
|
-
|
|
71
65
|
msgs << "\n"
|
|
72
66
|
|
|
73
|
-
|
|
74
|
-
msgs << c.print_results
|
|
75
|
-
end
|
|
67
|
+
msgs << collector.print_results
|
|
76
68
|
|
|
77
69
|
log :info, msgs.join
|
|
78
70
|
|
|
@@ -84,9 +76,9 @@ module TestProf
|
|
|
84
76
|
|
|
85
77
|
examples = Hash.new { |h, k| h[k] = [] }
|
|
86
78
|
|
|
87
|
-
|
|
79
|
+
results = collector.results.to_a
|
|
88
80
|
|
|
89
|
-
|
|
81
|
+
results
|
|
90
82
|
.map { |obj| obj[:loc] }.each do |location|
|
|
91
83
|
file, line = location.split(":")
|
|
92
84
|
examples[file] << line.to_i
|
|
@@ -114,7 +106,7 @@ module TestProf
|
|
|
114
106
|
|
|
115
107
|
private
|
|
116
108
|
|
|
117
|
-
attr_reader :
|
|
109
|
+
attr_reader :collector
|
|
118
110
|
|
|
119
111
|
def top_count
|
|
120
112
|
RSpecDissect.config.top_count
|
|
@@ -7,6 +7,9 @@ module TestProf
|
|
|
7
7
|
# RSpecDissect tracks how much time do you spend in `before` hooks
|
|
8
8
|
# and memoization helpers (i.e. `let`) in your tests.
|
|
9
9
|
module RSpecDissect
|
|
10
|
+
class Span < Struct.new(:id, :parent_id, :type, :duration, :meta)
|
|
11
|
+
end
|
|
12
|
+
|
|
10
13
|
module ExampleInstrumentation # :nodoc:
|
|
11
14
|
def run_before_example(*)
|
|
12
15
|
RSpecDissect.track(:before) { super }
|
|
@@ -15,12 +18,15 @@ module TestProf
|
|
|
15
18
|
|
|
16
19
|
module MemoizedInstrumentation # :nodoc:
|
|
17
20
|
def fetch_or_store(id, *)
|
|
21
|
+
return super if id == :subject
|
|
22
|
+
return @memoized[id] if @memoized[id]
|
|
23
|
+
|
|
18
24
|
res = nil
|
|
19
25
|
Thread.current[:_rspec_dissect_let_depth] ||= 0
|
|
20
26
|
Thread.current[:_rspec_dissect_let_depth] += 1
|
|
21
27
|
begin
|
|
22
28
|
res = if Thread.current[:_rspec_dissect_let_depth] == 1
|
|
23
|
-
RSpecDissect.track(:let, id) { super }
|
|
29
|
+
RSpecDissect.track(:let, name: id) { super }
|
|
24
30
|
else
|
|
25
31
|
super
|
|
26
32
|
end
|
|
@@ -33,45 +39,25 @@ module TestProf
|
|
|
33
39
|
|
|
34
40
|
# RSpecDisect configuration
|
|
35
41
|
class Configuration
|
|
36
|
-
MODES = %w[all let before].freeze
|
|
37
|
-
|
|
38
42
|
attr_accessor :top_count, :let_stats_enabled,
|
|
39
43
|
:let_top_count
|
|
40
44
|
|
|
41
45
|
alias_method :let_stats_enabled?, :let_stats_enabled
|
|
42
46
|
|
|
43
|
-
attr_reader :mode
|
|
44
|
-
|
|
45
47
|
def initialize
|
|
46
48
|
@let_stats_enabled = true
|
|
47
49
|
@let_top_count = (ENV["RD_PROF_LET_TOP"] || 3).to_i
|
|
48
50
|
@top_count = (ENV["RD_PROF_TOP"] || 5).to_i
|
|
49
51
|
@stamp = ENV["RD_PROF_STAMP"]
|
|
50
|
-
@mode = (ENV["RD_PROF"] == "1") ? "all" : ENV["RD_PROF"]
|
|
51
|
-
|
|
52
|
-
unless MODES.include?(mode)
|
|
53
|
-
raise "Unknown RSpecDissect mode: #{mode};" \
|
|
54
|
-
"available modes: #{MODES.join(", ")}"
|
|
55
|
-
end
|
|
56
52
|
|
|
57
53
|
RSpecStamp.config.tags = @stamp if stamp?
|
|
58
54
|
end
|
|
59
55
|
|
|
60
|
-
def let?
|
|
61
|
-
mode == "all" || mode == "let"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def before?
|
|
65
|
-
mode == "all" || mode == "before"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
56
|
def stamp?
|
|
69
57
|
!@stamp.nil?
|
|
70
58
|
end
|
|
71
59
|
end
|
|
72
60
|
|
|
73
|
-
METRICS = %w[before let].freeze
|
|
74
|
-
|
|
75
61
|
class << self
|
|
76
62
|
include Logging
|
|
77
63
|
|
|
@@ -89,56 +75,64 @@ module TestProf
|
|
|
89
75
|
RSpec::Core::MemoizedHelpers::ThreadsafeMemoized.prepend(MemoizedInstrumentation)
|
|
90
76
|
RSpec::Core::MemoizedHelpers::NonThreadSafeMemoized.prepend(MemoizedInstrumentation)
|
|
91
77
|
|
|
92
|
-
@data = {}
|
|
93
|
-
|
|
94
|
-
METRICS.each do |type|
|
|
95
|
-
@data["total_#{type}"] = 0.0
|
|
96
|
-
end
|
|
97
|
-
|
|
98
78
|
reset!
|
|
99
79
|
|
|
100
80
|
log :info, "RSpecDissect enabled"
|
|
101
81
|
end
|
|
102
82
|
|
|
103
|
-
def
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
delta = (TestProf.now - start)
|
|
107
|
-
type = type.to_s
|
|
108
|
-
@data[type][:time] += delta
|
|
109
|
-
@data[type][:meta] << meta unless meta.nil?
|
|
110
|
-
@data["total_#{type}"] += delta
|
|
111
|
-
res
|
|
83
|
+
def nextid
|
|
84
|
+
@last_id += 1
|
|
85
|
+
@last_id.to_s
|
|
112
86
|
end
|
|
113
87
|
|
|
114
|
-
def
|
|
115
|
-
|
|
116
|
-
@data[type.to_s] = {time: 0.0, meta: []}
|
|
117
|
-
end
|
|
88
|
+
def current_span
|
|
89
|
+
Thread.current[:_rspec_dissect_spans_stack].last
|
|
118
90
|
end
|
|
119
91
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
defined?(::RSpec::Core::MemoizedHelpers::ThreadsafeMemoized)
|
|
92
|
+
def span_stack
|
|
93
|
+
Thread.current[:_rspec_dissect_spans_stack]
|
|
123
94
|
end
|
|
124
95
|
|
|
125
|
-
def
|
|
126
|
-
|
|
96
|
+
def track(type, id: nextid, **meta)
|
|
97
|
+
span = Span.new(id, current_span&.id, type, 0.0, meta)
|
|
98
|
+
span_stack << span
|
|
99
|
+
|
|
100
|
+
begin
|
|
101
|
+
start = TestProf.now
|
|
102
|
+
res = yield
|
|
103
|
+
delta = (TestProf.now - start)
|
|
104
|
+
span.duration = delta
|
|
105
|
+
@spans << span
|
|
106
|
+
res
|
|
107
|
+
ensure
|
|
108
|
+
span_stack.pop
|
|
109
|
+
end
|
|
127
110
|
end
|
|
128
111
|
|
|
129
|
-
def
|
|
130
|
-
|
|
112
|
+
def populate_from_spans!(data)
|
|
113
|
+
data[:total_setup] = @spans.select { !_1.parent_id }.sum(&:duration)
|
|
114
|
+
data[:total_before_let] = @spans.select { _1.type == :let && _1.parent_id }.sum(&:duration).to_f
|
|
115
|
+
data[:total_lazy_let] = @spans.select { _1.type == :let && !_1.parent_id }.sum(&:duration).to_f
|
|
116
|
+
|
|
117
|
+
data[:top_lets] = @spans.select { _1.type == :let }
|
|
118
|
+
.group_by { _1.meta[:name] }
|
|
119
|
+
.transform_values! do |spans|
|
|
120
|
+
{name: spans.first.meta[:name], duration: spans.sum(&:duration), size: spans.size}
|
|
121
|
+
end
|
|
122
|
+
.values
|
|
123
|
+
.sort_by { -_1[:duration] }
|
|
124
|
+
.take(RSpecDissect.config.let_top_count)
|
|
131
125
|
end
|
|
132
126
|
|
|
133
|
-
def
|
|
134
|
-
@
|
|
127
|
+
def reset!
|
|
128
|
+
@last_id = 1
|
|
129
|
+
@spans = []
|
|
130
|
+
Thread.current[:_rspec_dissect_spans_stack] = []
|
|
135
131
|
end
|
|
136
132
|
end
|
|
137
133
|
end
|
|
138
134
|
end
|
|
139
135
|
|
|
140
|
-
require "test_prof/rspec_dissect/collectors/let"
|
|
141
|
-
require "test_prof/rspec_dissect/collectors/before"
|
|
142
136
|
require "test_prof/rspec_dissect/rspec"
|
|
143
137
|
|
|
144
138
|
TestProf.activate("RD_PROF") do
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
module TestProf
|
|
6
|
+
module RSpecStamp
|
|
7
|
+
module Parser
|
|
8
|
+
class Prism
|
|
9
|
+
def parse(code)
|
|
10
|
+
result = ::Prism.parse(code)
|
|
11
|
+
return unless result.success?
|
|
12
|
+
|
|
13
|
+
node = result.value.statements.body.first
|
|
14
|
+
return unless node.is_a?(::Prism::CallNode)
|
|
15
|
+
|
|
16
|
+
res = Result.new
|
|
17
|
+
res.fname =
|
|
18
|
+
if node.receiver
|
|
19
|
+
"#{node.receiver.full_name}.#{node.name}"
|
|
20
|
+
else
|
|
21
|
+
node.name.name
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
args = node.arguments&.arguments
|
|
25
|
+
return res if args.nil?
|
|
26
|
+
|
|
27
|
+
rest =
|
|
28
|
+
case (first = args.first).type
|
|
29
|
+
when :string_node
|
|
30
|
+
res.desc = first.content
|
|
31
|
+
args[1..]
|
|
32
|
+
when :constant_read_node, :constant_path_node
|
|
33
|
+
res.desc_const = first.full_name
|
|
34
|
+
args[1..]
|
|
35
|
+
else
|
|
36
|
+
args
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
rest.each do |arg|
|
|
40
|
+
case arg.type
|
|
41
|
+
when :symbol_node
|
|
42
|
+
res.add_tag(arg.value.to_sym)
|
|
43
|
+
when :keyword_hash_node
|
|
44
|
+
arg.elements.each do |assoc|
|
|
45
|
+
res.add_htag(
|
|
46
|
+
assoc.key.value.to_sym,
|
|
47
|
+
parse_htag_value(assoc.value)
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
res
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def parse_htag_value(node)
|
|
59
|
+
case node.type
|
|
60
|
+
when :true_node
|
|
61
|
+
true
|
|
62
|
+
when :false_node
|
|
63
|
+
false
|
|
64
|
+
when :integer_node, :float_node
|
|
65
|
+
node.value
|
|
66
|
+
when :string_node
|
|
67
|
+
node.unescaped
|
|
68
|
+
when :symbol_node
|
|
69
|
+
node.value.to_sym
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|