sidekiq 6.2.2 → 7.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +279 -11
  3. data/LICENSE.txt +9 -0
  4. data/README.md +45 -32
  5. data/bin/sidekiq +4 -9
  6. data/bin/sidekiqload +207 -117
  7. data/bin/sidekiqmon +4 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +331 -187
  13. data/lib/sidekiq/capsule.rb +127 -0
  14. data/lib/sidekiq/cli.rb +94 -81
  15. data/lib/sidekiq/client.rb +100 -96
  16. data/lib/sidekiq/{util.rb → component.rb} +14 -41
  17. data/lib/sidekiq/config.rb +274 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +26 -26
  21. data/lib/sidekiq/job.rb +371 -5
  22. data/lib/sidekiq/job_logger.rb +16 -28
  23. data/lib/sidekiq/job_retry.rb +85 -59
  24. data/lib/sidekiq/job_util.rb +105 -0
  25. data/lib/sidekiq/launcher.rb +106 -94
  26. data/lib/sidekiq/logger.rb +9 -44
  27. data/lib/sidekiq/manager.rb +40 -41
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +136 -0
  31. data/lib/sidekiq/middleware/chain.rb +96 -51
  32. data/lib/sidekiq/middleware/current_attributes.rb +56 -0
  33. data/lib/sidekiq/middleware/i18n.rb +6 -4
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +17 -4
  36. data/lib/sidekiq/paginator.rb +17 -9
  37. data/lib/sidekiq/processor.rb +60 -60
  38. data/lib/sidekiq/rails.rb +25 -6
  39. data/lib/sidekiq/redis_client_adapter.rb +96 -0
  40. data/lib/sidekiq/redis_connection.rb +17 -88
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +101 -44
  43. data/lib/sidekiq/testing/inline.rb +4 -4
  44. data/lib/sidekiq/testing.rb +41 -68
  45. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  46. data/lib/sidekiq/version.rb +2 -1
  47. data/lib/sidekiq/web/action.rb +3 -3
  48. data/lib/sidekiq/web/application.rb +47 -13
  49. data/lib/sidekiq/web/csrf_protection.rb +3 -3
  50. data/lib/sidekiq/web/helpers.rb +36 -33
  51. data/lib/sidekiq/web.rb +10 -17
  52. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  53. data/lib/sidekiq.rb +86 -201
  54. data/sidekiq.gemspec +12 -10
  55. data/web/assets/javascripts/application.js +131 -60
  56. data/web/assets/javascripts/base-charts.js +106 -0
  57. data/web/assets/javascripts/chart.min.js +13 -0
  58. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  59. data/web/assets/javascripts/dashboard-charts.js +166 -0
  60. data/web/assets/javascripts/dashboard.js +36 -273
  61. data/web/assets/javascripts/metrics.js +264 -0
  62. data/web/assets/stylesheets/application-dark.css +23 -23
  63. data/web/assets/stylesheets/application-rtl.css +2 -95
  64. data/web/assets/stylesheets/application.css +73 -402
  65. data/web/locales/ar.yml +70 -70
  66. data/web/locales/cs.yml +62 -62
  67. data/web/locales/da.yml +60 -53
  68. data/web/locales/de.yml +65 -65
  69. data/web/locales/el.yml +43 -24
  70. data/web/locales/en.yml +82 -69
  71. data/web/locales/es.yml +68 -68
  72. data/web/locales/fa.yml +65 -65
  73. data/web/locales/fr.yml +81 -67
  74. data/web/locales/gd.yml +99 -0
  75. data/web/locales/he.yml +65 -64
  76. data/web/locales/hi.yml +59 -59
  77. data/web/locales/it.yml +53 -53
  78. data/web/locales/ja.yml +73 -68
  79. data/web/locales/ko.yml +52 -52
  80. data/web/locales/lt.yml +66 -66
  81. data/web/locales/nb.yml +61 -61
  82. data/web/locales/nl.yml +52 -52
  83. data/web/locales/pl.yml +45 -45
  84. data/web/locales/pt-br.yml +63 -55
  85. data/web/locales/pt.yml +51 -51
  86. data/web/locales/ru.yml +67 -66
  87. data/web/locales/sv.yml +53 -53
  88. data/web/locales/ta.yml +60 -60
  89. data/web/locales/uk.yml +62 -61
  90. data/web/locales/ur.yml +64 -64
  91. data/web/locales/vi.yml +67 -67
  92. data/web/locales/zh-cn.yml +43 -16
  93. data/web/locales/zh-tw.yml +42 -8
  94. data/web/views/_footer.erb +6 -3
  95. data/web/views/_job_info.erb +18 -2
  96. data/web/views/_metrics_period_select.erb +12 -0
  97. data/web/views/_nav.erb +1 -1
  98. data/web/views/_paging.erb +2 -0
  99. data/web/views/_poll_link.erb +3 -6
  100. data/web/views/_summary.erb +7 -7
  101. data/web/views/busy.erb +44 -28
  102. data/web/views/dashboard.erb +44 -12
  103. data/web/views/layout.erb +1 -1
  104. data/web/views/metrics.erb +82 -0
  105. data/web/views/metrics_for_job.erb +68 -0
  106. data/web/views/morgue.erb +5 -9
  107. data/web/views/queue.erb +24 -24
  108. data/web/views/queues.erb +4 -2
  109. data/web/views/retries.erb +5 -9
  110. data/web/views/scheduled.erb +12 -13
  111. metadata +62 -31
  112. data/LICENSE +0 -9
  113. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  114. data/lib/sidekiq/delay.rb +0 -41
  115. data/lib/sidekiq/exception_handler.rb +0 -27
  116. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  117. data/lib/sidekiq/extensions/active_record.rb +0 -43
  118. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  119. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  120. data/lib/sidekiq/worker.rb +0 -244
data/bin/sidekiqload CHANGED
@@ -1,87 +1,71 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ #
4
+ # bin/sidekiqload is a helpful script to load test and
5
+ # performance tune Sidekiq's core. It creates 500,000 no-op
6
+ # jobs and executes them as fast as possible.
7
+ # Example Usage:
8
+ #
9
+ # > RUBY_YJIT_ENABLE=1 LATENCY=0 THREADS=10 bin/sidekiqload
10
+ # Result: Done, 500000 jobs in 20.264945 sec, 24673 jobs/sec
11
+ #
12
+ # Use LATENCY=1 to get a more real world network setup
13
+ # but you'll need to setup and start toxiproxy as noted below.
14
+ #
15
+ # Use AJ=1 to test ActiveJob instead of plain old Sidekiq::Jobs so
16
+ # you can see the runtime performance difference between the two APIs.
17
+ #
18
+ # None of this script is considered a public API and may change over time.
19
+ #
20
+
3
21
  # Quiet some warnings we see when running in warning mode:
4
22
  # RUBYOPT=-w bundle exec sidekiq
5
23
  $TESTING = false
24
+ puts RUBY_DESCRIPTION
25
+ puts(%w[THREADS LATENCY AJ PROFILE].map { |x| "#{x}: #{ENV[x] || "nil"}" }.join(", "))
6
26
 
7
- #require 'ruby-prof'
8
- require 'bundler/setup'
27
+ require "ruby-prof" if ENV["PROFILE"]
28
+ require "bundler/setup"
9
29
  Bundler.require(:default, :load_test)
10
30
 
11
- require_relative '../lib/sidekiq/cli'
12
- require_relative '../lib/sidekiq/launcher'
31
+ latency = Integer(ENV["LATENCY"] || 1)
32
+ if latency > 0
33
+ # brew tap shopify/shopify
34
+ # brew install toxiproxy
35
+ # run `toxiproxy-server` in a separate terminal window.
36
+ require "toxiproxy"
37
+ # simulate a non-localhost network for realer-world conditions.
38
+ # adding 1ms of network latency has an ENORMOUS impact on benchmarks
39
+ Toxiproxy.populate([{
40
+ name: "redis",
41
+ listen: "127.0.0.1:6380",
42
+ upstream: "127.0.0.1:6379"
43
+ }])
44
+ end
13
45
 
14
- include Sidekiq::Util
46
+ if ENV["AJ"]
47
+ require "active_job"
48
+ puts "Using ActiveJob #{ActiveJob::VERSION::STRING}"
49
+ ActiveJob::Base.queue_adapter = :sidekiq
50
+ ActiveJob::Base.logger.level = Logger::WARN
15
51
 
16
- Sidekiq.configure_server do |config|
17
- config.options[:concurrency] = 10
18
- config.redis = { db: 13, port: 6380, driver: :hiredis }
19
- config.options[:queues] << 'default'
20
- config.logger.level = Logger::ERROR
21
- config.average_scheduled_poll_interval = 2
22
- config.reliable! if defined?(Sidekiq::Pro)
52
+ class LoadJob < ActiveJob::Base
53
+ def perform(idx, ts = nil)
54
+ puts(Time.now.to_f - ts) if !ts.nil?
55
+ end
56
+ end
23
57
  end
24
58
 
25
59
  class LoadWorker
26
- include Sidekiq::Worker
60
+ include Sidekiq::Job
27
61
  sidekiq_options retry: 1
28
62
  sidekiq_retry_in do |x|
29
63
  1
30
64
  end
31
65
 
32
- def perform(idx, ts=nil)
33
- puts(Time.now.to_f - ts) if ts != nil
34
- #raise idx.to_s if idx % 100 == 1
35
- end
36
- end
37
-
38
- # brew tap shopify/shopify
39
- # brew install toxiproxy
40
- # gem install toxiproxy
41
- # run `toxiproxy-server` in a separate terminal window.
42
- require 'toxiproxy'
43
- # simulate a non-localhost network for realer-world conditions.
44
- # adding 1ms of network latency has an ENORMOUS impact on benchmarks
45
- Toxiproxy.populate([{
46
- "name": "redis",
47
- "listen": "127.0.0.1:6380",
48
- "upstream": "127.0.0.1:6379"
49
- }])
50
-
51
- self_read, self_write = IO.pipe
52
- %w(INT TERM TSTP TTIN).each do |sig|
53
- begin
54
- trap sig do
55
- self_write.puts(sig)
56
- end
57
- rescue ArgumentError
58
- puts "Signal #{sig} not supported"
59
- end
60
- end
61
-
62
- Sidekiq.redis {|c| c.flushdb}
63
- def handle_signal(launcher, sig)
64
- Sidekiq.logger.debug "Got #{sig} signal"
65
- case sig
66
- when 'INT'
67
- # Handle Ctrl-C in JRuby like MRI
68
- # http://jira.codehaus.org/browse/JRUBY-4637
69
- raise Interrupt
70
- when 'TERM'
71
- # Heroku sends TERM and then waits 30 seconds for process to exit.
72
- raise Interrupt
73
- when 'TSTP'
74
- Sidekiq.logger.info "Received TSTP, no longer accepting new work"
75
- launcher.quiet
76
- when 'TTIN'
77
- Thread.list.each do |thread|
78
- Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['label']}"
79
- if thread.backtrace
80
- Sidekiq.logger.warn thread.backtrace.join("\n")
81
- else
82
- Sidekiq.logger.warn "<no backtrace available>"
83
- end
84
- end
66
+ def perform(idx, ts = nil)
67
+ puts(Time.now.to_f - ts) if !ts.nil?
68
+ # raise idx.to_s if idx % 100 == 1
85
69
  end
86
70
  end
87
71
 
@@ -89,69 +73,175 @@ def Process.rss
89
73
  `ps -o rss= -p #{Process.pid}`.chomp.to_i
90
74
  end
91
75
 
92
- iter = 10
93
- count = 10_000
76
+ class Loader
77
+ def initialize
78
+ @iter = ENV["GC"] ? 10 : 500
79
+ @count = Integer(ENV["COUNT"] || 1_000)
80
+ @latency = Integer(ENV["LATENCY"] || 1)
81
+ end
82
+
83
+ def configure
84
+ @x = Sidekiq.configure_embed do |config|
85
+ config.redis = {db: 13, port: ((@latency > 0) ? 6380 : 6379)}
86
+ config.concurrency = Integer(ENV.fetch("THREADS", "10"))
87
+ # config.redis = { db: 13, port: 6380, driver: :hiredis}
88
+ config.queues = %w[default]
89
+ config.logger.level = Logger::WARN
90
+ config.average_scheduled_poll_interval = 2
91
+ config.reliable! if defined?(Sidekiq::Pro)
92
+ end
94
93
 
95
- iter.times do
96
- arr = Array.new(count) do
97
- []
94
+ @self_read, @self_write = IO.pipe
95
+ %w[INT TERM TSTP TTIN].each do |sig|
96
+ trap sig do
97
+ @self_write.puts(sig)
98
+ end
99
+ rescue ArgumentError
100
+ puts "Signal #{sig} not supported"
101
+ end
98
102
  end
99
- count.times do |idx|
100
- arr[idx][0] = idx
103
+
104
+ def handle_signal(sig)
105
+ launcher = @x
106
+ Sidekiq.logger.debug "Got #{sig} signal"
107
+ case sig
108
+ when "INT"
109
+ # Handle Ctrl-C in JRuby like MRI
110
+ # http://jira.codehaus.org/browse/JRUBY-4637
111
+ raise Interrupt
112
+ when "TERM"
113
+ # Heroku sends TERM and then waits 30 seconds for process to exit.
114
+ raise Interrupt
115
+ when "TSTP"
116
+ Sidekiq.logger.info "Received TSTP, no longer accepting new work"
117
+ launcher.quiet
118
+ when "TTIN"
119
+ Thread.list.each do |thread|
120
+ Sidekiq.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread["label"]}"
121
+ if thread.backtrace
122
+ Sidekiq.logger.warn thread.backtrace.join("\n")
123
+ else
124
+ Sidekiq.logger.warn "<no backtrace available>"
125
+ end
126
+ end
127
+ end
101
128
  end
102
- Sidekiq::Client.push_bulk('class' => LoadWorker, 'args' => arr)
103
- end
104
- Sidekiq.logger.error "Created #{count*iter} jobs"
105
129
 
106
- start = Time.now
130
+ def setup
131
+ Sidekiq.logger.error("Setup RSS: #{Process.rss}")
132
+ Sidekiq.redis { |c| c.flushdb }
133
+ start = Time.now
134
+ if ENV["AJ"]
135
+ @iter.times do
136
+ @count.times do |idx|
137
+ LoadJob.perform_later(idx)
138
+ end
139
+ end
140
+ else
141
+ @iter.times do
142
+ arr = Array.new(@count) { |idx| [idx] }
143
+ Sidekiq::Client.push_bulk("class" => LoadWorker, "args" => arr)
144
+ end
145
+ end
146
+ Sidekiq.logger.warn "Created #{@count * @iter} jobs in #{Time.now - start} sec"
147
+ end
107
148
 
108
- Monitoring = Thread.new do
109
- watchdog("monitor thread") do
110
- while true
111
- sleep 0.2
112
- qsize = Sidekiq.redis do |conn|
113
- conn.llen "queue:default"
149
+ def monitor
150
+ @monitor = Thread.new do
151
+ GC.start
152
+ loop do
153
+ sleep 0.2
154
+ qsize = Sidekiq.redis do |conn|
155
+ conn.llen "queue:default"
156
+ end
157
+ total = qsize
158
+ if total == 0
159
+ ending = Time.now - @start
160
+ size = @iter * @count
161
+ Sidekiq.logger.error("Done, #{size} jobs in #{ending} sec, #{(size / ending).to_i} jobs/sec")
162
+ Sidekiq.logger.error("Ending RSS: #{Process.rss}")
163
+ Sidekiq.logger.error("Now here's the latency for three jobs")
164
+
165
+ if ENV["AJ"]
166
+ LoadJob.perform_later(1, Time.now.to_f)
167
+ LoadJob.perform_later(2, Time.now.to_f)
168
+ LoadJob.perform_later(3, Time.now.to_f)
169
+ else
170
+ LoadWorker.perform_async(1, Time.now.to_f)
171
+ LoadWorker.perform_async(2, Time.now.to_f)
172
+ LoadWorker.perform_async(3, Time.now.to_f)
173
+ end
174
+
175
+ sleep 0.1
176
+ @x.stop
177
+ Process.kill("INT", $$)
178
+ break
179
+ end
114
180
  end
115
- total = qsize
116
- #Sidekiq.logger.error("RSS: #{Process.rss} Pending: #{total}")
117
- if total == 0
118
- Sidekiq.logger.error("Done, #{iter * count} jobs in #{Time.now - start} sec")
119
- Sidekiq.logger.error("Now here's the latency for three jobs")
181
+ end
182
+ end
120
183
 
121
- LoadWorker.perform_async(1, Time.now.to_f)
122
- LoadWorker.perform_async(2, Time.now.to_f)
123
- LoadWorker.perform_async(3, Time.now.to_f)
184
+ def with_latency(latency, &block)
185
+ Sidekiq.logger.error "Simulating #{latency}ms of latency between Sidekiq and redis"
186
+ if latency > 0
187
+ Toxiproxy[:redis].downstream(:latency, latency: latency).apply(&block)
188
+ else
189
+ yield
190
+ end
191
+ end
124
192
 
125
- sleep 0.2
126
- exit(0)
193
+ def run(name)
194
+ Sidekiq.logger.warn("Starting #{name}")
195
+ monitor
196
+
197
+ if ENV["PROFILE"]
198
+ RubyProf.exclude_threads = [@monitor]
199
+ RubyProf.start
200
+ elsif ENV["GC"]
201
+ GC.start
202
+ GC.compact
203
+ GC.disable
204
+ Sidekiq.logger.error("GC Start RSS: #{Process.rss}")
205
+ end
206
+ @start = Time.now
207
+ with_latency(@latency) do
208
+ @x.run
209
+
210
+ while (readable_io = IO.select([@self_read]))
211
+ signal = readable_io.first[0].gets.strip
212
+ handle_signal(signal)
127
213
  end
128
214
  end
215
+ # normal
216
+ rescue Interrupt
217
+ rescue => e
218
+ raise e if $DEBUG
219
+ warn e.message
220
+ warn e.backtrace.join("\n")
221
+ exit 1
222
+ ensure
223
+ @x.stop
129
224
  end
130
- end
131
225
 
132
- begin
133
- #RubyProf::exclude_threads = [ Monitoring ]
134
- #RubyProf.start
135
- fire_event(:startup)
136
- Sidekiq.logger.error "Simulating 1ms of latency between Sidekiq and redis"
137
- Toxiproxy[:redis].downstream(:latency, latency: 1).apply do
138
- launcher = Sidekiq::Launcher.new(Sidekiq.options)
139
- launcher.run
140
-
141
- while readable_io = IO.select([self_read])
142
- signal = readable_io.first[0].gets.strip
143
- handle_signal(launcher, signal)
226
+ def done
227
+ Sidekiq.logger.error("GC End RSS: #{Process.rss}") if ENV["GC"]
228
+ if ENV["PROFILE"]
229
+ Sidekiq.logger.error("Profiling...")
230
+ result = RubyProf.stop
231
+ printer = RubyProf::GraphHtmlPrinter.new(result)
232
+ printer.print(File.new("output.html", "w"), min_percent: 1)
144
233
  end
145
234
  end
146
- rescue SystemExit => e
147
- #Sidekiq.logger.error("Profiling...")
148
- #result = RubyProf.stop
149
- #printer = RubyProf::GraphHtmlPrinter.new(result)
150
- #printer.print(File.new("output.html", "w"), :min_percent => 1)
151
- # normal
152
- rescue => e
153
- raise e if $DEBUG
154
- STDERR.puts e.message
155
- STDERR.puts e.backtrace.join("\n")
156
- exit 1
157
235
  end
236
+
237
+ ll = Loader.new
238
+ ll.configure
239
+
240
+ if ENV["WARM"]
241
+ ll.setup
242
+ ll.run("warmup")
243
+ end
244
+
245
+ ll.setup
246
+ ll.run("load")
247
+ ll.done
data/bin/sidekiqmon CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'sidekiq/monitor'
3
+ require "sidekiq/monitor"
4
+
5
+ # disable the Redis connection pool logging
6
+ Sidekiq.default_configuration.logger.level = :warn
4
7
 
5
8
  section = "all"
6
9
  section = ARGV[0] if ARGV.size == 1
@@ -0,0 +1,57 @@
1
+ require "rails/generators/named_base"
2
+
3
+ module Sidekiq
4
+ module Generators # :nodoc:
5
+ class JobGenerator < ::Rails::Generators::NamedBase # :nodoc:
6
+ desc "This generator creates a Sidekiq Job in app/sidekiq and a corresponding test"
7
+
8
+ check_class_collision suffix: "Job"
9
+
10
+ def self.default_generator_root
11
+ File.dirname(__FILE__)
12
+ end
13
+
14
+ def create_job_file
15
+ template "job.rb.erb", File.join("app/sidekiq", class_path, "#{file_name}_job.rb")
16
+ end
17
+
18
+ def create_test_file
19
+ return unless test_framework
20
+
21
+ if test_framework == :rspec
22
+ create_job_spec
23
+ else
24
+ create_job_test
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def create_job_spec
31
+ template_file = File.join(
32
+ "spec/sidekiq",
33
+ class_path,
34
+ "#{file_name}_job_spec.rb"
35
+ )
36
+ template "job_spec.rb.erb", template_file
37
+ end
38
+
39
+ def create_job_test
40
+ template_file = File.join(
41
+ "test/sidekiq",
42
+ class_path,
43
+ "#{file_name}_job_test.rb"
44
+ )
45
+ template "job_test.rb.erb", template_file
46
+ end
47
+
48
+ def file_name
49
+ @_file_name ||= super.sub(/_?job\z/i, "")
50
+ end
51
+
52
+ def test_framework
53
+ ::Rails.application.config.generators.options[:rails][:test_framework]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,6 +1,6 @@
1
1
  <% module_namespacing do -%>
2
- class <%= class_name %>Worker
3
- include Sidekiq::Worker
2
+ class <%= class_name %>Job
3
+ include Sidekiq::Job
4
4
 
5
5
  def perform(*args)
6
6
  # Do something
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
  <% module_namespacing do -%>
3
- RSpec.describe <%= class_name %>Worker, type: :worker do
3
+ RSpec.describe <%= class_name %>Job, type: :job do
4
4
  pending "add some examples to (or delete) #{__FILE__}"
5
5
  end
6
6
  <% end -%>
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
  <% module_namespacing do -%>
3
- class <%= class_name %>WorkerTest < Minitest::Test
3
+ class <%= class_name %>JobTest < Minitest::Test
4
4
  def test_example
5
5
  skip "add some examples to (or delete) #{__FILE__}"
6
6
  end