test-prof 1.0.3 → 1.0.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3ea5b7078b12209f32a60554f25acffccd184bdf852ebfe14cad161ded2c180
4
- data.tar.gz: 34e61ec6d58e5688b441ea09c1be524ae563f9ca82f334541c3f2c3197787cbd
3
+ metadata.gz: 2f55e42ee372a9ebe0f8981487931bf70ab3209e38163b32f84a7553bdfb4249
4
+ data.tar.gz: 3ca0352ca4671c11d18937d3ee1de361322c827671d199d079b1460667c32a1c
5
5
  SHA512:
6
- metadata.gz: d866bfe03650d81cb2dd5b698b5de5703a8fa505c7c8d0b0e4283b8130eb774ac43c5cca6a750fef6f8342acdec7e518613489e898fd354f133e1793df833e45
7
- data.tar.gz: cf92cef8973837d2e09ff3bcc4449fb0675ebfca3f10fc75a314f10d885ad92aaae2f6fac0cd99e7250eb57985208e6d6f53b88d101f177709a172b8fcae1815
6
+ metadata.gz: 00dcd7797667e28b54fe32e36392e44b80a1dd6fdcc04b8a0565084311c5420bbe017eecba2ae553b84b2cdd6bc92886f15a919da8e1ba7a293aa737bc2c81da
7
+ data.tar.gz: eed04e297d23872fae979d7da614d4cf2730c4414b7eb26ad14095e619eeabd872619cd3e51293a4949fc04f214807cb10425da4a7ebe343df8a8f9af766f413
data/CHANGELOG.md CHANGED
@@ -2,6 +2,48 @@
2
2
 
3
3
  ## master (unrealeased)
4
4
 
5
+ ## 1.0.7 (2021-08-30)
6
+
7
+ - Fix access to `let_it_be` variables in `after(:all)` hook. ([@cbarton][])
8
+
9
+ - Add support for using the before_all hook with Rails' parallelize feature (using processes). ([@peret][])
10
+
11
+ Make sure to include `TestProf::BeforeAll::Minitest` before you call `parallelize`.
12
+
13
+ ## 1.0.6 (2021-06-23)
14
+
15
+ - Fix Spring detection when `DISABLE_SPRING=1` is used. ([@palkan][])
16
+
17
+ - Make `before_all` in Minitest inheritable. ([@palkan][])
18
+
19
+ ## 1.0.5 (2021-05-13)
20
+
21
+ - Fix logging regression when no newline has been added. ([@palkan][])
22
+
23
+ ## 1.0.4 (2021-05-12)
24
+
25
+ - Add ability to use custom logger. ([@palkan][])
26
+
27
+ ```ruby
28
+ TestProf.configure do |config|
29
+ config.logger = Logger.new($stdout, level: Logger::WARN)
30
+ end
31
+ ```
32
+
33
+ - Add `nate_heckler` mode for FactoryProf. ([@palkan][])
34
+
35
+ Drop this into your `rails_helper.rb` or `test_helper.rb`:
36
+
37
+ ```ruby
38
+ require "test_prof/factory_prof/nate_heckler"
39
+ ```
40
+
41
+ And for every test run see the overall factories usage:
42
+
43
+ ```sh
44
+ [TEST PROF INFO] Time spent in factories: 04:31.222 (54% of total time)
45
+ ```
46
+
5
47
  ## 1.0.3 (2021-04-30)
6
48
 
7
49
  - Minor fixes.
@@ -150,7 +192,7 @@ end
150
192
  ```
151
193
 
152
194
  - Print warning when `ActiveRecordSharedConnection` is used in the version of Rails
153
- supporting `lock_threads` (5.1+). ([@palkan][])
195
+ supporting `lock_threads` (5.1+). ([@palkan][])
154
196
 
155
197
  ## 0.9.0 (2019-05-14)
156
198
 
@@ -208,20 +250,20 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md)
208
250
  [@palkan]: https://github.com/palkan
209
251
  [@marshall-lee]: https://github.com/marshall-lee
210
252
  [@danielwestendorf]: https://github.com/danielwestendorf
211
- [@Shkrt]: https://github.com/Shkrt
212
- [@IDolgirev]: https://github.com/IDolgirev
253
+ [@shkrt]: https://github.com/Shkrt
254
+ [@idolgirev]: https://github.com/IDolgirev
213
255
  [@desoleary]: https://github.com/desoleary
214
256
  [@rabotyaga]: https://github.com/rabotyaga
215
- [@Vasfed]: https://github.com/Vasfed
257
+ [@vasfed]: https://github.com/Vasfed
216
258
  [@szemek]: https://github.com/szemek
217
259
  [@mkldon]: https://github.com/mkldon
218
260
  [@dmagro]: https://github.com/dmagro
219
261
  [@danielwaterworth]: https://github.com/danielwaterworth
220
- [@Envek]: https://github.com/Envek
262
+ [@envek]: https://github.com/Envek
221
263
  [@tyleriguchi]: https://github.com/tyleriguchi
222
264
  [@lostie]: https://github.com/lostie
223
265
  [@pirj]: https://github.com/pirj
224
- [@LynxEyes]: https://github.com/LynxEyes
266
+ [@lynxeyes]: https://github.com/LynxEyes
225
267
  [@stefkin]: https://github.com/stefkin
226
268
  [@jaimerson]: https://github.com/jaimerson
227
269
  [@alexvko]: https://github.com/alexvko
@@ -102,7 +102,7 @@ module TestProf
102
102
  def quoted(val)
103
103
  if val.is_a?(Array)
104
104
  val.map { |v| quoted(v) }
105
- elsif val.is_a?(ActiveRecord::Relation::QueryAttribute)
105
+ elsif val.is_a?(ActiveModel::Attribute)
106
106
  quoted(val.value_for_database)
107
107
  else
108
108
  ActiveRecord::Base.connection.quote(val)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/factory_prof"
4
+
5
+ # A standalone Factory Prof printer which is meant to be always enabled
6
+
7
+ TestProf::FactoryProf.patch!
8
+
9
+ started_at = TestProf.now
10
+ at_exit do
11
+ TestProf::FactoryProf::Printers::NateHeckler.dump(
12
+ TestProf::FactoryProf.result, start_time: started_at
13
+ )
14
+ end
15
+
16
+ TestProf::FactoryProf.start
@@ -11,7 +11,7 @@ module TestProf::FactoryProf
11
11
  class << self
12
12
  include TestProf::Logging
13
13
 
14
- def dump(result)
14
+ def dump(result, **)
15
15
  return log(:info, "No factories detected") if result.raw_stats == {}
16
16
  report_data = {
17
17
  total_stacks: result.stacks.size,
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+
5
+ module TestProf::FactoryProf
6
+ module Printers
7
+ # See https://twitter.com/nateberkopec/status/1389945187766456333
8
+ module NateHeckler # :nodoc: all
9
+ class << self
10
+ using TestProf::FloatDuration
11
+ include TestProf::Logging
12
+
13
+ def dump(result, start_time:)
14
+ return if result.raw_stats == {}
15
+
16
+ total_time = result.stats.sum { |stat| stat[:top_level_time] }
17
+ total_run_time = TestProf.now - start_time
18
+
19
+ percentage = ((total_time / total_run_time) * 100).round(2)
20
+
21
+ log :info, "Time spent in factories: #{total_time.duration} (#{percentage}% of total time)"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/ext/float_duration"
4
+
3
5
  module TestProf::FactoryProf
4
6
  module Printers
5
7
  module Simple # :nodoc: all
6
8
  class << self
9
+ using TestProf::FloatDuration
7
10
  include TestProf::Logging
8
11
 
9
- def dump(result)
12
+ def dump(result, start_time:)
10
13
  return log(:info, "No factories detected") if result.raw_stats == {}
11
14
  msgs = []
12
15
 
16
+ total_run_time = TestProf.now - start_time
13
17
  total_count = result.stats.sum { |stat| stat[:total_count] }
14
18
  total_top_level_count = result.stats.sum { |stat| stat[:top_level_count] }
15
19
  total_time = result.stats.sum { |stat| stat[:top_level_time] }
@@ -21,7 +25,7 @@ module TestProf::FactoryProf
21
25
 
22
26
  Total: #{total_count}
23
27
  Total top-level: #{total_top_level_count}
24
- Total time: #{format("%.4f", total_time)}s
28
+ Total time: #{total_time.duration} (out of #{total_run_time.duration})
25
29
  Total uniq factories: #{total_uniq_factories}
26
30
 
27
31
  total top-level total time time per call top-level time name
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "test_prof/factory_prof/printers/simple"
4
4
  require "test_prof/factory_prof/printers/flamegraph"
5
+ require "test_prof/factory_prof/printers/nate_heckler"
5
6
  require "test_prof/factory_prof/factory_builders/factory_bot"
6
7
  require "test_prof/factory_prof/factory_builders/fabrication"
7
8
 
@@ -14,10 +15,19 @@ module TestProf
14
15
 
15
16
  # FactoryProf configuration
16
17
  class Configuration
17
- attr_accessor :mode
18
+ attr_accessor :mode, :printer
18
19
 
19
20
  def initialize
20
21
  @mode = ENV["FPROF"] == "flamegraph" ? :flamegraph : :simple
22
+ @printer =
23
+ case ENV["FPROF"]
24
+ when "flamegraph"
25
+ Printers::Flamegraph
26
+ when "nate_heckler"
27
+ Printers::NateHeckler
28
+ else
29
+ Printers::Simple
30
+ end
21
31
  end
22
32
 
23
33
  # Whether we want to generate flamegraphs
@@ -74,7 +84,15 @@ module TestProf
74
84
 
75
85
  log :info, "FactoryProf enabled (#{config.mode} mode)"
76
86
 
87
+ patch!
88
+ end
89
+
90
+ def patch!
91
+ return if @patched
92
+
77
93
  FACTORY_BUILDERS.each(&:patch)
94
+
95
+ @patched = true
78
96
  end
79
97
 
80
98
  # Inits FactoryProf and setups at exit hook,
@@ -82,9 +100,11 @@ module TestProf
82
100
  def run
83
101
  init
84
102
 
85
- printer = config.flamegraph? ? Printers::Flamegraph : Printers::Simple
103
+ printer = config.printer
104
+
105
+ started_at = TestProf.now
86
106
 
87
- at_exit { printer.dump(result) }
107
+ at_exit { printer.dump(result, start_time: started_at) }
88
108
 
89
109
  start
90
110
  end
@@ -4,23 +4,29 @@ module TestProf
4
4
  # Helper for output printing
5
5
  module Logging
6
6
  COLORS = {
7
- info: "\e[34m", # blue
8
- warn: "\e[33m", # yellow
9
- error: "\e[31m" # red
7
+ INFO: "\e[34m", # blue
8
+ WARN: "\e[33m", # yellow
9
+ ERROR: "\e[31m" # red
10
10
  }.freeze
11
11
 
12
- def log(level, msg)
13
- TestProf.config.output.puts(build_log_msg(level, msg))
14
- end
12
+ class Formatter
13
+ def call(severity, _time, progname, msg)
14
+ colorize(severity.to_sym, "[#{progname} #{severity}] #{msg}\n")
15
+ end
15
16
 
16
- def build_log_msg(level, msg)
17
- colorize(level, "[TEST PROF #{level.to_s.upcase}] #{msg}")
18
- end
17
+ private
18
+
19
+ def colorize(level, msg)
20
+ return msg unless TestProf.config.color?
19
21
 
20
- def colorize(level, msg)
21
- return msg unless TestProf.config.color?
22
+ return msg unless COLORS.key?(level)
22
23
 
23
- "#{COLORS[level]}#{msg}\e[0m"
24
+ "#{COLORS[level]}#{msg}\e[0m"
25
+ end
26
+ end
27
+
28
+ def log(level, msg)
29
+ TestProf.config.logger.public_send(level, "TEST PROF") { msg }
24
30
  end
25
31
  end
26
32
  end
@@ -2,6 +2,22 @@
2
2
 
3
3
  require "test_prof/before_all"
4
4
 
5
+ Minitest.singleton_class.prepend(Module.new do
6
+ attr_reader :previous_klass
7
+ @previous_klass = nil
8
+
9
+ def run_one_method(klass, method_name)
10
+ return super unless klass.respond_to?(:parallelized) && klass.parallelized
11
+
12
+ if @previous_klass && @previous_klass != klass
13
+ @previous_klass.before_all_executor&.deactivate!
14
+ end
15
+ @previous_klass = klass
16
+
17
+ super
18
+ end
19
+ end)
20
+
5
21
  module TestProf
6
22
  module BeforeAll
7
23
  # Add before_all hook to Minitest: wrap all examples into a transaction and
@@ -9,13 +25,15 @@ module TestProf
9
25
  module Minitest # :nodoc: all
10
26
  class Executor
11
27
  attr_reader :active, :block, :captured_ivars, :teardown_block, :current_test_object,
12
- :setup_fixtures
28
+ :setup_fixtures, :parent
13
29
 
14
30
  alias_method :active?, :active
15
31
  alias_method :setup_fixtures?, :setup_fixtures
16
32
 
17
- def initialize(setup_fixtures: false, &block)
18
- @setup_fixtures = setup_fixtures
33
+ def initialize(setup_fixtures: false, parent: nil, &block)
34
+ @parent = parent
35
+ # Fixtures must be instantiated if any of the executors needs them
36
+ @setup_fixtures = setup_fixtures || parent&.setup_fixtures
19
37
  @block = block
20
38
  @captured_ivars = []
21
39
  end
@@ -28,7 +46,9 @@ module TestProf
28
46
  @current_test_object = test_object
29
47
 
30
48
  return restore_ivars(test_object) if active?
49
+
31
50
  @active = true
51
+
32
52
  BeforeAll.setup_fixtures(test_object) if setup_fixtures?
33
53
  BeforeAll.begin_transaction do
34
54
  capture!(test_object)
@@ -36,20 +56,20 @@ module TestProf
36
56
  end
37
57
 
38
58
  def deactivate!
59
+ return unless active
60
+
39
61
  @active = false
40
62
 
41
- current_test_object&.instance_eval(&teardown_block) if teardown_block
63
+ perform_teardown(current_test_object)
42
64
 
43
65
  @current_test_object = nil
44
66
  BeforeAll.rollback_transaction
45
67
  end
46
68
 
47
69
  def capture!(test_object)
48
- return unless block
49
-
50
70
  before_ivars = test_object.instance_variables
51
71
 
52
- test_object.instance_eval(&block)
72
+ perform_setup(test_object)
53
73
 
54
74
  (test_object.instance_variables - before_ivars).each do |ivar|
55
75
  captured_ivars << [ivar, test_object.instance_variable_get(ivar)]
@@ -64,19 +84,76 @@ module TestProf
64
84
  )
65
85
  end
66
86
  end
87
+
88
+ def perform_setup(test_object)
89
+ parent&.perform_setup(test_object)
90
+ test_object.instance_eval(&block) if block
91
+ end
92
+
93
+ def perform_teardown(test_object)
94
+ current_test_object&.instance_eval(&teardown_block) if teardown_block
95
+ parent&.perform_teardown(test_object)
96
+ end
67
97
  end
68
98
 
69
99
  class << self
70
100
  def included(base)
71
101
  base.extend ClassMethods
102
+
103
+ base.cattr_accessor :parallelized
104
+ if base.respond_to?(:parallelize_teardown)
105
+ base.parallelize_teardown do
106
+ last_klass = ::Minitest.previous_klass
107
+ if last_klass&.respond_to?(:parallelized) && last_klass&.parallelized
108
+ last_klass.before_all_executor&.deactivate!
109
+ end
110
+ end
111
+ end
112
+
113
+ if base.respond_to?(:parallelize)
114
+ base.singleton_class.prepend(Module.new do
115
+ def parallelize(workers: :number_of_processors, with: :processes)
116
+ # super.parallelize returns nil when no parallelization is set up
117
+ if super(workers: workers, with: with).nil?
118
+ return
119
+ end
120
+
121
+ case with
122
+ when :processes
123
+ self.parallelized = true
124
+ when :threads
125
+ warn "!!! before_all is not implemented for parallalization with threads and " \
126
+ "could work incorrectly"
127
+ else
128
+ warn "!!! tests are using an unknown parallelization strategy and before_all " \
129
+ "could work incorrectly"
130
+ end
131
+ end
132
+ end)
133
+ end
72
134
  end
73
135
  end
74
136
 
75
137
  module ClassMethods
76
- attr_accessor :before_all_executor
138
+ attr_writer :before_all_executor
139
+
140
+ def before_all_executor
141
+ return @before_all_executor if instance_variable_defined?(:@before_all_executor)
142
+
143
+ @before_all_executor = if superclass.respond_to?(:before_all_executor)
144
+ superclass.before_all_executor
145
+ end
146
+ end
77
147
 
78
148
  def before_all(setup_fixtures: BeforeAll.config.setup_fixtures, &block)
79
- self.before_all_executor = Executor.new(setup_fixtures: setup_fixtures, &block)
149
+ self.before_all_executor = Executor.new(
150
+ setup_fixtures: setup_fixtures,
151
+ parent: before_all_executor,
152
+ &block
153
+ )
154
+
155
+ # Do not add patches multiple times
156
+ return if before_all_executor.parent
80
157
 
81
158
  prepend(Module.new do
82
159
  def before_setup
@@ -89,13 +166,13 @@ module TestProf
89
166
  def run(*)
90
167
  super
91
168
  ensure
92
- before_all_executor&.deactivate!
169
+ before_all_executor&.deactivate! unless parallelized
93
170
  end
94
171
  end)
95
172
  end
96
173
 
97
174
  def after_all(&block)
98
- self.before_all_executor ||= Executor.new
175
+ self.before_all_executor = Executor.new(parent: before_all_executor)
99
176
  before_all_executor.teardown(&block)
100
177
  end
101
178
  end
@@ -107,7 +107,7 @@ module TestProf
107
107
  define_method(identifier) do
108
108
  # Trying to detect the context
109
109
  # Based on https://github.com/rspec/rspec-rails/commit/7cb796db064f58da7790a92e73ab906ef50b1f34
110
- if @__inspect_output.include?("before(:context)") || @__inspect_output.include?("before_all")
110
+ if /(before|after)\(:context\)/.match?(@__inspect_output) || @__inspect_output.include?("before_all")
111
111
  instance_variable_get(:"#{PREFIX}#{identifier}")
112
112
  else
113
113
  # Fallback to let definition
@@ -39,7 +39,7 @@ module TestProf
39
39
  acc << if v.nil?
40
40
  k.to_sym
41
41
  else
42
- Hash[k.to_sym, v.to_sym]
42
+ {k.to_sym => v.to_sym}
43
43
  end
44
44
  end
45
45
  end
@@ -12,7 +12,7 @@ module TestProf
12
12
 
13
13
  @data = Hash.new do |h, k|
14
14
  h[k] = {value: k, count: 0, time: 0.0}
15
- h[k].merge!(Hash[events.map { |event| [event, 0.0] }]) unless
15
+ h[k].merge!(events.map { |event| [event, 0.0] }.to_h) unless
16
16
  events.empty?
17
17
  h[k]
18
18
  end
@@ -53,11 +53,9 @@ module TestProf
53
53
  def fetch_events_data
54
54
  return {} unless @events_profiler
55
55
 
56
- Hash[
57
- @events_profiler.profilers.map do |profiler|
58
- [profiler.event, profiler.time]
59
- end
60
- ]
56
+ @events_profiler.profilers.map do |profiler|
57
+ [profiler.event, profiler.time]
58
+ end.to_h
61
59
  end
62
60
  end
63
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "1.0.3"
4
+ VERSION = "1.0.7"
5
5
  end
data/lib/test_prof.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
+ require "logger"
5
+
4
6
  require "test_prof/version"
5
7
  require "test_prof/logging"
6
8
  require "test_prof/utils"
@@ -44,6 +46,13 @@ module TestProf
44
46
  defined?(Minitest)
45
47
  end
46
48
 
49
+ # Returns true if Spring is used and not disabled
50
+ def spring?
51
+ # See https://github.com/rails/spring/blob/577cf01f232bb6dbd0ade7df2df2ac209697e741/lib/spring/binstub.rb
52
+ disabled = ENV["DISABLE_SPRING"]
53
+ defined?(::Spring::Application) && (disabled.nil? || disabled.empty? || disabled == "0")
54
+ end
55
+
47
56
  # Returns the current process time
48
57
  def now
49
58
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
@@ -63,7 +72,7 @@ module TestProf
63
72
  # equal to the provided value (if any).
64
73
  # Contains workaround for applications using Spring.
65
74
  def activate(env_var, val = nil)
66
- if defined?(::Spring::Application)
75
+ if spring?
67
76
  notify_spring_detected
68
77
  ::Spring.after_fork do
69
78
  activate!(env_var, val) do
@@ -130,7 +139,7 @@ module TestProf
130
139
 
131
140
  # TestProf configuration
132
141
  class Configuration
133
- attr_accessor :output, # IO to write output messages.
142
+ attr_accessor :output, # IO to write logs
134
143
  :color, # Whether to colorize output or not
135
144
  :output_dir, # Directory to store artifacts
136
145
  :timestamps, # Whether to use timestamped names for artifacts,
@@ -145,12 +154,16 @@ module TestProf
145
154
  end
146
155
 
147
156
  def color?
148
- color == true
157
+ color == true && output.is_a?(IO) && output.tty?
149
158
  end
150
159
 
151
160
  def timestamps?
152
161
  timestamps == true
153
162
  end
163
+
164
+ def logger
165
+ @logger ||= Logger.new(output, formatter: Logging::Formatter.new)
166
+ end
154
167
  end
155
168
  end
156
169
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-prof
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-30 00:00:00.000000000 Z
11
+ date: 2021-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -174,7 +174,9 @@ files:
174
174
  - lib/test_prof/factory_prof/factory_bot_patch.rb
175
175
  - lib/test_prof/factory_prof/factory_builders/fabrication.rb
176
176
  - lib/test_prof/factory_prof/factory_builders/factory_bot.rb
177
+ - lib/test_prof/factory_prof/nate_heckler.rb
177
178
  - lib/test_prof/factory_prof/printers/flamegraph.rb
179
+ - lib/test_prof/factory_prof/printers/nate_heckler.rb
178
180
  - lib/test_prof/factory_prof/printers/simple.rb
179
181
  - lib/test_prof/logging.rb
180
182
  - lib/test_prof/recipes/logging.rb
@@ -214,11 +216,12 @@ homepage: http://github.com/test-prof/test-prof
214
216
  licenses:
215
217
  - MIT
216
218
  metadata:
217
- bug_tracker_uri: http://github.com/test-prof/test-prof/issues
219
+ bug_tracker_uri: https://github.com/test-prof/test-prof/issues
218
220
  changelog_uri: https://github.com/test-prof/test-prof/blob/master/CHANGELOG.md
219
221
  documentation_uri: https://test-prof.evilmartians.io/
220
222
  homepage_uri: https://test-prof.evilmartians.io/
221
- source_code_uri: http://github.com/test-prof/test-prof
223
+ source_code_uri: https://github.com/test-prof/test-prof
224
+ funding_uri: https://github.com/sponsors/test-prof
222
225
  post_install_message:
223
226
  rdoc_options: []
224
227
  require_paths: