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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
  4. data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
  5. data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples.rb +5 -5
  6. data/lib/{test_prof/cops → rubocop/test_prof}/plugin.rb +2 -2
  7. data/lib/rubocop/test_prof.rb +9 -0
  8. data/lib/test-prof.rb +5 -0
  9. data/lib/test_prof/event_prof/custom_events/sidekiq_inline.rb +1 -1
  10. data/lib/test_prof/event_prof/custom_events/sidekiq_jobs.rb +1 -1
  11. data/lib/test_prof/memory_prof/printer.rb +31 -0
  12. data/lib/test_prof/memory_prof/rspec.rb +2 -0
  13. data/lib/test_prof/memory_prof/tracker.rb +11 -1
  14. data/lib/test_prof/memory_prof.rb +8 -3
  15. data/lib/test_prof/recipes/rspec/let_it_be.rb +1 -1
  16. data/lib/test_prof/rspec_dissect/collector.rb +58 -0
  17. data/lib/test_prof/rspec_dissect/rspec.rb +13 -21
  18. data/lib/test_prof/rspec_dissect.rb +45 -51
  19. data/lib/test_prof/rspec_stamp/parser/prism.rb +75 -0
  20. data/lib/test_prof/rspec_stamp/parser/ripper.rb +128 -0
  21. data/lib/test_prof/rspec_stamp/parser.rb +8 -120
  22. data/lib/test_prof/rspec_stamp.rb +1 -0
  23. data/lib/test_prof/rubocop.rb +10 -9
  24. data/lib/test_prof/tps_prof/profiler.rb +52 -15
  25. data/lib/test_prof/tps_prof/reporter/text.rb +27 -7
  26. data/lib/test_prof/tps_prof/rspec.rb +25 -2
  27. data/lib/test_prof/tps_prof.rb +69 -4
  28. data/lib/test_prof/version.rb +1 -1
  29. metadata +14 -13
  30. data/lib/test_prof/rspec_dissect/collectors/base.rb +0 -70
  31. data/lib/test_prof/rspec_dissect/collectors/before.rb +0 -19
  32. data/lib/test_prof/rspec_dissect/collectors/let.rb +0 -39
  33. /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/its.rb +0 -0
  34. /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/line_range_helpers.rb +0 -0
  35. /data/lib/{test_prof → rubocop/test_prof}/cops/rspec/aggregate_examples/metadata_helpers.rb +0 -0
  36. /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: 119118a5840991b0d1bde28e49aca9f3f72f77f89ba57704c53ce2e8a023ad06
4
- data.tar.gz: 6f124770cfeba6346f744dbf66a00b118c6c207711af9f18977abea153de4f7a
3
+ metadata.gz: b843db2a99bf53e8c380e748bb73340c573e1de8082f76519530eb0ddf32cb95
4
+ data.tar.gz: 94048d78eaf4857b0a869fad4d551701b0501074cb97ff9ac9ad13a5b69eee03
5
5
  SHA512:
6
- metadata.gz: 6827f8b914ce78da9cabff0dd04ef643c3571e22864a1c6c215028fd4a2e9f79dbbab0d69e7ac9184753460aee96a2202a9201f7bdf2177074b42de47e893ac4
7
- data.tar.gz: 4beacf5a982c0fe64c1c363660f2985be9dd6470ac847eb10e5ef4cd3ddf3360681e8c09fb511d5a99418629b455755e93824768c21520d0c754a908a7e7080c
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,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "test_prof/cops/rspec/language"
3
+ require_relative "../language"
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "test_prof/cops/rspec/language"
3
+ require_relative "../language"
4
4
 
5
5
  module RuboCop
6
6
  module Cop
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "test_prof/cops/rspec/aggregate_examples/line_range_helpers"
4
- require "test_prof/cops/rspec/aggregate_examples/metadata_helpers"
5
- require "test_prof/cops/rspec/aggregate_examples/node_matchers"
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
- require "test_prof/cops/rspec/aggregate_examples/its"
8
- require "test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects"
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: "test_prof",
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,3 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "test_prof"
4
+
5
+ # For RuboCop plugin
6
+ module RuboCop
7
+ autoload :TestProf, "rubocop/test_prof"
8
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  TestProf::EventProf::CustomEvents.register("sidekiq.inline") do
4
4
  if TestProf.require(
5
- "sidekiq/testing",
5
+ "sidekiq",
6
6
  <<~MSG
7
7
  Failed to load Sidekiq.
8
8
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  TestProf::EventProf::CustomEvents.register("sidekiq.jobs") do
4
4
  if TestProf.require(
5
- "sidekiq/testing",
5
+ "sidekiq",
6
6
  <<~MSG
7
7
  Failed to load Sidekiq.
8
8
 
@@ -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.hooks_memory}
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 == "alloc") ? :alloc : :rss
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 instance_methods.include?(identifier) && File.basename(__FILE__) == File.basename(instance_method(identifier).source_location[0])
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
- @collectors = []
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
- collectors.each { |c| c.populate!(data) }
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
- collectors.each do |c|
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
- all_results = collectors.inject([]) { |acc, c| acc + c.results.to_a }
79
+ results = collector.results.to_a
88
80
 
89
- all_results
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 :collectors
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 track(type, meta = nil)
104
- start = TestProf.now
105
- res = yield
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 reset!
115
- METRICS.each do |type|
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
- # Whether we are able to track `let` usage
121
- def memoization_available?
122
- defined?(::RSpec::Core::MemoizedHelpers::ThreadsafeMemoized)
92
+ def span_stack
93
+ Thread.current[:_rspec_dissect_spans_stack]
123
94
  end
124
95
 
125
- def time_for(key)
126
- @data[key.to_s][:time]
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 meta_for(key)
130
- @data[key.to_s][:meta]
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 total_time_for(key)
134
- @data["total_#{key}"]
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