File: rspec_dissect.rb

package info (click to toggle)
ruby-test-prof 0.12.2%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 508 kB
  • sloc: ruby: 4,075; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 3,485 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true

require "test_prof/rspec_stamp"
require "test_prof/logging"

module TestProf
  # RSpecDissect tracks how much time do you spend in `before` hooks
  # and memoization helpers (i.e. `let`) in your tests.
  module RSpecDissect
    module ExampleInstrumentation # :nodoc:
      def run_before_example(*)
        RSpecDissect.track(:before) { super }
      end
    end

    module MemoizedInstrumentation # :nodoc:
      def fetch_or_store(id, *)
        res = nil
        Thread.current[:_rspec_dissect_let_depth] ||= 0
        Thread.current[:_rspec_dissect_let_depth] += 1
        begin
          res = if Thread.current[:_rspec_dissect_let_depth] == 1
            RSpecDissect.track(:let, id) { super }
          else
            super
          end
        ensure
          Thread.current[:_rspec_dissect_let_depth] -= 1
        end
        res
      end
    end

    # RSpecDisect configuration
    class Configuration
      MODES = %w[all let before].freeze

      attr_accessor :top_count, :let_stats_enabled,
        :let_top_count

      alias let_stats_enabled? let_stats_enabled

      attr_reader :mode

      def initialize
        @let_stats_enabled = true
        @let_top_count = (ENV["RD_PROF_LET_TOP"] || 3).to_i
        @top_count = (ENV["RD_PROF_TOP"] || 5).to_i
        @stamp = ENV["RD_PROF_STAMP"]
        @mode = ENV["RD_PROF"] == "1" ? "all" : ENV["RD_PROF"]

        unless MODES.include?(mode)
          raise "Unknown RSpecDissect mode: #{mode};" \
                "available modes: #{MODES.join(", ")}"
        end

        RSpecStamp.config.tags = @stamp if stamp?
      end

      def let?
        mode == "all" || mode == "let"
      end

      def before?
        mode == "all" || mode == "before"
      end

      def stamp?
        !@stamp.nil?
      end
    end

    METRICS = %w[before let].freeze

    class << self
      include Logging

      def config
        @config ||= Configuration.new
      end

      def configure
        yield config
      end

      def init
        RSpec::Core::Example.prepend(ExampleInstrumentation)

        RSpec::Core::MemoizedHelpers::ThreadsafeMemoized.prepend(MemoizedInstrumentation)
        RSpec::Core::MemoizedHelpers::NonThreadSafeMemoized.prepend(MemoizedInstrumentation)

        @data = {}

        METRICS.each do |type|
          @data["total_#{type}"] = 0.0
        end

        reset!

        log :info, "RSpecDissect enabled"
      end

      def track(type, meta = nil)
        start = TestProf.now
        res = yield
        delta = (TestProf.now - start)
        type = type.to_s
        @data[type][:time] += delta
        @data[type][:meta] << meta unless meta.nil?
        @data["total_#{type}"] += delta
        res
      end

      def reset!
        METRICS.each do |type|
          @data[type.to_s] = {time: 0.0, meta: []}
        end
      end

      # Whether we are able to track `let` usage
      def memoization_available?
        defined?(::RSpec::Core::MemoizedHelpers::ThreadsafeMemoized)
      end

      def time_for(key)
        @data[key.to_s][:time]
      end

      def meta_for(key)
        @data[key.to_s][:meta]
      end

      def total_time_for(key)
        @data["total_#{key}"]
      end
    end
  end
end

require "test_prof/rspec_dissect/collectors/let"
require "test_prof/rspec_dissect/collectors/before"
require "test_prof/rspec_dissect/rspec" if TestProf.rspec?

TestProf.activate("RD_PROF") do
  TestProf::RSpecDissect.init
end