sidetiq 0.4.0.rc2 → 0.4.0.rc3

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
  SHA1:
3
- metadata.gz: 456f6b922646c7e198b83f34b299e07f984a6c0f
4
- data.tar.gz: 3401166967dab3170b26f961147d014950e68dfe
3
+ metadata.gz: 9aef5c4cda17d14aa2592dc263cbd945d06e45de
4
+ data.tar.gz: 692bdc4bacfea9a9f641b0c947e226dd58da20f4
5
5
  SHA512:
6
- metadata.gz: 34d95105066f87aa9736bf3e4e454df16cf065f841051809f60e5f3aa1123d750fcaa317c545d6344ad6cfcb3ad199b987cb52a2ebf3ae21794ce13910485242
7
- data.tar.gz: de936682d4191d86e36e94439f97d2ce549ddd671b793eca78f1d39b33462c6e8373513cf66ec8f7b7ce5f90dcf1378b43a513745c56a61b4f50070916ffa405
6
+ metadata.gz: a4b9e85e2ce6a206e84b3f15338fc2fec527978d13b617232692cc0a0098469bf0f00d03b017e1323667681179374ab024cd5f3f3a4dac46ab04b5f458cf12ad
7
+ data.tar.gz: 91a79a02ee8718e1ea787474b12cbc94bf78ae49d3a38641511a4238902f871d5bc21e01e53091e9a3b99bd27fe359d7728b0160bf59e66426b73320f3cfacd4
data/CHANGELOG.md CHANGED
@@ -1,6 +1,8 @@
1
1
  0.4.0
2
2
  -----
3
3
 
4
+ - Schedules are now stored on the workers directly instead of in a
5
+ pseudo-global, mutable Hash.
4
6
  - Clock now start looping automatically if `Sidekiq.server?` returns true.
5
7
  - Show job history in web extension.
6
8
  - Integrate with Sidekiq's exception handling/reporting in critical parts.
data/lib/sidetiq/actor.rb CHANGED
@@ -27,8 +27,8 @@ module Sidetiq
27
27
  def link_to_sidekiq_manager
28
28
  Sidekiq::CLI.instance.launcher.manager.link(current_actor)
29
29
  rescue NoMethodError
30
- warn "Can't link #{self.class.name}. Sidekiq::Manager not running. Retrying ..."
31
- after(1) { link_to_sidekiq_manager }
30
+ warn "Can't link #{self.class.name}. Sidekiq::Manager not running. Retrying in 5 seconds ..."
31
+ after(5) { link_to_sidekiq_manager }
32
32
  end
33
33
 
34
34
  def log_call(call)
data/lib/sidetiq/api.rb CHANGED
@@ -3,12 +3,12 @@ module Sidetiq
3
3
  module API
4
4
  # Public: Returns an Array of workers including Sidetiq::Schedulable.
5
5
  def workers
6
- schedules.keys
6
+ Sidetiq::Schedulable.subclasses(true)
7
7
  end
8
8
 
9
9
  # Public: Returns a Hash of Sidetiq::Schedule instances.
10
10
  def schedules
11
- clock.schedules.dup
11
+ workers.map(&:schedule)
12
12
  end
13
13
 
14
14
  # Public: Currently scheduled recurring jobs.
data/lib/sidetiq/clock.rb CHANGED
@@ -8,7 +8,6 @@ module Sidetiq
8
8
 
9
9
  def initialize # :nodoc:
10
10
  super
11
- @schedules = {}
12
11
  end
13
12
 
14
13
  # Public: Get the schedule for `worker`.
@@ -22,7 +21,9 @@ module Sidetiq
22
21
  #
23
22
  # Returns a Sidetiq::Schedule instances.
24
23
  def schedule_for(worker)
25
- schedules[worker] ||= Sidetiq::Schedule.new
24
+ if worker.respond_to?(:schedule)
25
+ worker.schedule
26
+ end
26
27
  end
27
28
 
28
29
  # Public: Issue a single clock tick.
@@ -35,8 +36,8 @@ module Sidetiq
35
36
  # Returns a hash of Sidetiq::Schedule instances.
36
37
  def tick
37
38
  tick = gettime
38
- schedules.each do |worker, sched|
39
- Sidetiq.handler.dispatch(worker,sched, tick)
39
+ Sidetiq.workers.each do |worker|
40
+ Sidetiq.handler.dispatch(worker, tick)
40
41
  end
41
42
  end
42
43
 
@@ -3,18 +3,20 @@ module Sidetiq
3
3
  include Logging
4
4
  include Sidekiq::ExceptionHandler
5
5
 
6
- def dispatch(worker, sched, tick)
7
- return unless sched.schedule_next?(tick)
6
+ def dispatch(worker, tick)
7
+ schedule = worker.schedule
8
+
9
+ return unless schedule.schedule_next?(tick)
8
10
 
9
11
  Lock::Redis.new(worker).synchronize do |redis|
10
- if sched.backfill? && (last = worker.last_scheduled_occurrence) > 0
12
+ if schedule.backfill? && (last = worker.last_scheduled_occurrence) > 0
11
13
  last = Sidetiq.config.utc ? Time.at(last).utc : Time.at(last)
12
- sched.occurrences_between(last + 1, tick).each do |past_t|
14
+ schedule.occurrences_between(last + 1, tick).each do |past_t|
13
15
  enqueue(worker, past_t, redis)
14
16
  end
15
17
  end
16
18
 
17
- enqueue(worker, sched.next_occurrence(tick), redis)
19
+ enqueue(worker, schedule.next_occurrence(tick), redis)
18
20
  end
19
21
  rescue StandardError => e
20
22
  handle_exception(e, context: "Sidetiq::Handler#dispatch")
@@ -39,12 +39,10 @@ module Sidetiq
39
39
 
40
40
  def save_entry_for_worker(entry, worker)
41
41
  Sidekiq.redis do |redis|
42
- redis.pipelined do |pipe|
43
- list_name = "sidetiq:#{worker.class.name}:history"
42
+ list_name = "sidetiq:#{worker.class.name}:history"
44
43
 
45
- pipe.lpush(list_name, JSON.dump(entry))
46
- pipe.ltrim(list_name, 0, Sidetiq.config.worker_history - 1)
47
- end
44
+ redis.lpush(list_name, JSON.dump(entry))
45
+ redis.ltrim(list_name, 0, Sidetiq.config.worker_history - 1)
48
46
  end
49
47
  end
50
48
  end
@@ -11,14 +11,24 @@ module Sidetiq
11
11
  # recurrence { daily }
12
12
  # end
13
13
  module Schedulable
14
+ extend SubclassTracking
15
+
14
16
  module ClassMethods
15
17
  include Logging
18
+ include SubclassTracking
19
+
20
+ attr_writer :schedule
16
21
 
17
22
  # Public: Returns a Float timestamp of the last scheduled run.
18
23
  def last_scheduled_occurrence
19
24
  get_timestamp "last"
20
25
  end
21
26
 
27
+ # Public: Returns the Sidetiq::Schedule for this worker.
28
+ def schedule
29
+ @schedule ||= Sidetiq::Schedule.new
30
+ end
31
+
22
32
  # Public: Returns a Float timestamp of the next scheduled run.
23
33
  def next_scheduled_occurrence
24
34
  get_timestamp "next"
@@ -32,7 +42,6 @@ module Sidetiq
32
42
  end
33
43
 
34
44
  def recurrence(options = {}, &block) # :nodoc:
35
- schedule = Sidetiq.clock.schedule_for(self)
36
45
  schedule.instance_eval(&block)
37
46
  schedule.set_options(options)
38
47
  end
@@ -47,7 +56,11 @@ module Sidetiq
47
56
  end
48
57
 
49
58
  def self.included(klass) # :nodoc:
59
+ super
60
+
50
61
  klass.extend(Sidetiq::Schedulable::ClassMethods)
62
+ klass.extend(Sidetiq::SubclassTracking)
63
+ subclasses << klass
51
64
  end
52
65
  end
53
66
  end
@@ -0,0 +1,20 @@
1
+ module Sidetiq
2
+ module SubclassTracking
3
+ def subclasses(deep = false)
4
+ @subclasses ||= []
5
+
6
+ if deep
7
+ @subclasses.inject([]) do |all, subclass|
8
+ (all << subclass) + subclass.subclasses(true)
9
+ end
10
+ else
11
+ @subclasses
12
+ end
13
+ end
14
+
15
+ def inherited(klass)
16
+ super
17
+ subclasses << klass
18
+ end
19
+ end
20
+ end
@@ -11,7 +11,7 @@ module Sidetiq
11
11
  PATCH = 0
12
12
 
13
13
  # Public: Sidetiq version suffix.
14
- SUFFIX = 'rc2'
14
+ SUFFIX = 'rc3'
15
15
 
16
16
  # Public: String representation of the current Sidetiq version.
17
17
  STRING = [MAJOR, MINOR, PATCH, SUFFIX].compact.join('.')
@@ -1,13 +1,13 @@
1
1
  <div class="span3">
2
2
  <ul class="nav nav-list sidetiq-sidenav">
3
3
  <li>
4
- <a href="/sidetiq">
4
+ <a href="<%= " #{root_path}sidetiq" %>">
5
5
  <i class="icon-chevron-right"></i>
6
6
  Home
7
7
  </a>
8
8
  </li>
9
9
  <li>
10
- <a href="/sidetiq/locks">
10
+ <a href="<%= "#{root_path}sidetiq/locks" %>">
11
11
  <i class="icon-chevron-right"></i>
12
12
  Locks
13
13
  </a>
@@ -1,19 +1,19 @@
1
1
  <div class="span3">
2
2
  <ul class="nav nav-list sidetiq-sidenav">
3
3
  <li>
4
- <a href="/sidetiq">
4
+ <a href="<%= "#{root_path}sidetiq" %>">
5
5
  <i class="icon-chevron-right"></i>
6
6
  Home
7
7
  </a>
8
8
  </li>
9
9
  <li>
10
- <a href="/sidetiq/<%= @worker.name %>/schedule">
10
+ <a href="<%= "#{root_path}sidetiq/#{@worker.name}/schedule" %>">
11
11
  <i class="icon-chevron-right"></i>
12
12
  Job Schedule
13
13
  </a>
14
14
  </li>
15
15
  <li>
16
- <a href="/sidetiq/<%= @worker.name %>/history">
16
+ <a href="<%= "#{root_path}sidetiq/#{@worker.name}/history" %>">
17
17
  <i class="icon-chevron-right"></i>
18
18
  Job History
19
19
  </a>
@@ -11,7 +11,7 @@
11
11
  <%= erb File.read(File.join(File.dirname(__FILE__), 'views', '_home_nav.erb')) %>
12
12
 
13
13
  <div class="span9">
14
- <% if @schedules.length > 0 %>
14
+ <% if @workers.length > 0 %>
15
15
  <table class="table table-striped table-bordered table-white" style="width: 100%; margin: 0; table-layout:fixed;">
16
16
  <thead>
17
17
  <th style="width: 50%">Worker</th>
@@ -19,7 +19,8 @@
19
19
  <th style="width: 30%">Next Run</th>
20
20
  <th style="width: 10%">Actions</th>
21
21
  </thead>
22
- <% @schedules.each do |worker, schedule| %>
22
+ <% @workers.each do |worker| %>
23
+ <% schedule = worker.schedule %>
23
24
  <tr>
24
25
  <td>
25
26
  <a href="<%= "#{root_path}sidetiq/#{worker.name}/schedule" %>"><%= worker.name %></a>
data/lib/sidetiq/web.rb CHANGED
@@ -6,7 +6,7 @@ module Sidetiq
6
6
 
7
7
  def self.registered(app)
8
8
  app.get "/sidetiq" do
9
- @schedules = Sidetiq.schedules
9
+ @workers = Sidetiq.workers
10
10
  @time = Sidetiq.clock.gettime
11
11
  erb File.read(File.join(VIEWS, 'sidetiq.erb'))
12
12
  end
@@ -22,9 +22,11 @@ module Sidetiq
22
22
 
23
23
  @time = Sidetiq.clock.gettime
24
24
 
25
- @worker, @schedule = Sidetiq.schedules.select do |worker, _|
25
+ @worker = Sidetiq.workers.detect do |worker|
26
26
  worker.name == name
27
- end.flatten
27
+ end
28
+
29
+ @schedule = @worker.schedule
28
30
 
29
31
  erb File.read(File.join(VIEWS, 'schedule.erb'))
30
32
  end
@@ -34,12 +36,12 @@ module Sidetiq
34
36
 
35
37
  @time = Sidetiq.clock.gettime
36
38
 
37
- @worker, @schedule = Sidetiq.schedules.select do |worker, _|
39
+ @worker = Sidetiq.workers.detect do |worker|
38
40
  worker.name == name
39
- end.flatten
41
+ end
40
42
 
41
43
  @history = Sidekiq.redis do |redis|
42
- redis.lrange("sidetiq:#{@worker.name}:history", 0, -1)
44
+ redis.lrange("sidetiq:#{name}:history", 0, -1)
43
45
  end
44
46
 
45
47
  erb File.read(File.join(VIEWS, 'history.erb'))
@@ -48,9 +50,9 @@ module Sidetiq
48
50
  app.post "/sidetiq/:name/trigger" do
49
51
  halt 404 unless (name = params[:name])
50
52
 
51
- worker, _ = Sidetiq.schedules.select do |w, _|
52
- w.name == name
53
- end.flatten
53
+ worker = Sidetiq.workers.detect do |worker|
54
+ worker.name == name
55
+ end
54
56
 
55
57
  worker.perform_async
56
58
 
data/lib/sidetiq.rb CHANGED
@@ -2,6 +2,7 @@
2
2
  require 'ostruct'
3
3
  require 'singleton'
4
4
  require 'socket'
5
+ require 'time'
5
6
 
6
7
  # gems
7
8
  require 'ice_cube'
@@ -12,6 +13,7 @@ require 'celluloid'
12
13
  require 'sidetiq/config'
13
14
  require 'sidetiq/logging'
14
15
  require 'sidetiq/api'
16
+ require 'sidetiq/subclass_tracking'
15
17
  require 'sidetiq/clock'
16
18
  require 'sidetiq/handler'
17
19
  require 'sidetiq/lock/meta_data'
@@ -58,3 +60,7 @@ module Sidetiq
58
60
  Sidetiq::Supervisor.handler
59
61
  end
60
62
  end
63
+
64
+ if Sidekiq.server?
65
+ Sidetiq::Supervisor.run!
66
+ end
@@ -1,4 +1,9 @@
1
1
  class LastAndScheduledTicksWorker
2
+ include Sidekiq::Worker
3
+ include Sidetiq::Schedulable
4
+
5
+ recurrence { hourly }
6
+
2
7
  def perform(last_tick, scheduled_tick)
3
8
  end
4
9
  end
@@ -1,4 +1,9 @@
1
1
  class LastTickWorker
2
+ include Sidekiq::Worker
3
+ include Sidetiq::Schedulable
4
+
5
+ recurrence { hourly }
6
+
2
7
  def perform(last_tick)
3
8
  end
4
9
  end
@@ -1,7 +1,9 @@
1
1
  class OptionalArgumentWorker
2
- include Sidekiq::Worker
2
+ include Sidekiq::Worker
3
3
  include Sidetiq::Schedulable
4
4
 
5
+ recurrence { hourly }
6
+
5
7
  def perform(last_tick = nil)
6
8
  end
7
9
  end
@@ -2,6 +2,8 @@ class SimpleWorker
2
2
  include Sidekiq::Worker
3
3
  include Sidetiq::Schedulable
4
4
 
5
+ recurrence { daily }
6
+
5
7
  def perform
6
8
  end
7
9
  end
@@ -1,4 +1,9 @@
1
1
  class SplatArgsWorker
2
+ include Sidekiq::Worker
3
+ include Sidetiq::Schedulable
4
+
5
+ recurrence { hourly }
6
+
2
7
  def perform(arg1, *args)
3
8
  end
4
9
  end
data/test/test_clock.rb CHANGED
@@ -18,6 +18,7 @@ class TestClock < Sidetiq::TestCase
18
18
 
19
19
  def test_backfilling
20
20
  BackfillWorker.jobs.clear
21
+ Sidetiq.stubs(:workers).returns([BackfillWorker])
21
22
  start = Sidetiq::Schedule::START_TIME
22
23
 
23
24
  BackfillWorker.stubs(:last_scheduled_occurrence).returns(start.to_f)
@@ -32,10 +33,7 @@ class TestClock < Sidetiq::TestCase
32
33
  end
33
34
 
34
35
  def test_enqueues_jobs_by_schedule
35
- schedule = Sidetiq::Schedule.new
36
- schedule.daily
37
-
38
- clock.stubs(:schedules).returns(SimpleWorker => schedule)
36
+ Sidetiq.stubs(:workers).returns([SimpleWorker])
39
37
 
40
38
  SimpleWorker.expects(:perform_at).times(10)
41
39
 
@@ -51,13 +49,11 @@ class TestClock < Sidetiq::TestCase
51
49
  end
52
50
 
53
51
  def test_enqueues_jobs_with_default_last_tick_arg_on_first_run
54
- schedule = Sidetiq::Schedule.new
55
- schedule.hourly
56
-
57
52
  time = Time.local(2011, 1, 1, 1, 30)
58
53
 
59
54
  clock.stubs(:gettime).returns(time, time + 3600)
60
- clock.stubs(:schedules).returns(LastTickWorker => schedule)
55
+
56
+ Sidetiq.stubs(:workers).returns([LastTickWorker])
61
57
 
62
58
  expected_first_tick = time + 1800
63
59
  expected_second_tick = expected_first_tick + 3600
@@ -71,13 +67,11 @@ class TestClock < Sidetiq::TestCase
71
67
  end
72
68
 
73
69
  def test_enqueues_jobs_with_last_run_timestamp_and_next_run_timestamp
74
- schedule = Sidetiq::Schedule.new
75
- schedule.hourly
76
-
77
70
  time = Time.local(2011, 1, 1, 1, 30)
78
71
 
79
72
  clock.stubs(:gettime).returns(time, time + 3600)
80
- clock.stubs(:schedules).returns(LastAndScheduledTicksWorker => schedule)
73
+
74
+ Sidetiq.stubs(:workers).returns([LastAndScheduledTicksWorker])
81
75
 
82
76
  expected_first_tick = time + 1800
83
77
  expected_second_tick = expected_first_tick + 3600
@@ -95,13 +89,11 @@ class TestClock < Sidetiq::TestCase
95
89
  end
96
90
 
97
91
  def test_enqueues_jobs_with_last_run_timestamp_if_optional_argument
98
- schedule = Sidetiq::Schedule.new
99
- schedule.hourly
100
-
101
92
  time = Time.local(2011, 1, 1, 1, 30)
102
93
 
103
94
  clock.stubs(:gettime).returns(time, time + 3600)
104
- clock.stubs(:schedules).returns(OptionalArgumentWorker => schedule)
95
+
96
+ Sidetiq.stubs(:workers).returns([OptionalArgumentWorker])
105
97
 
106
98
  expected_first_tick = time + 1800
107
99
 
@@ -111,13 +103,11 @@ class TestClock < Sidetiq::TestCase
111
103
  end
112
104
 
113
105
  def test_enqueues_jobs_correctly_for_splat_args_perform_methods
114
- schedule = Sidetiq::Schedule.new
115
- schedule.hourly
116
-
117
106
  time = Time.local(2011, 1, 1, 1, 30)
118
107
 
119
108
  clock.stubs(:gettime).returns(time, time + 3600)
120
- clock.stubs(:schedules).returns(SplatArgsWorker => schedule)
109
+
110
+ Sidetiq.stubs(:workers).returns([SplatArgsWorker])
121
111
 
122
112
  expected_first_tick = time + 1800
123
113
 
data/test/test_sidetiq.rb CHANGED
@@ -4,13 +4,11 @@ class TestSidetiq < Sidetiq::TestCase
4
4
  def test_schedules
5
5
  schedules = Sidetiq.schedules
6
6
 
7
- assert_equal 2, schedules.length
7
+ assert_includes schedules, ScheduledWorker.schedule
8
+ assert_includes schedules, BackfillWorker.schedule
8
9
 
9
- assert_includes schedules.keys, ScheduledWorker
10
- assert_includes schedules.keys, BackfillWorker
11
-
12
- assert_kind_of Sidetiq::Schedule, schedules[ScheduledWorker]
13
- assert_kind_of Sidetiq::Schedule, schedules[BackfillWorker]
10
+ assert_kind_of Sidetiq::Schedule, ScheduledWorker.schedule
11
+ assert_kind_of Sidetiq::Schedule, BackfillWorker.schedule
14
12
  end
15
13
 
16
14
  def test_workers
@@ -18,7 +16,6 @@ class TestSidetiq < Sidetiq::TestCase
18
16
 
19
17
  assert_includes workers, ScheduledWorker
20
18
  assert_includes workers, BackfillWorker
21
- assert_equal 2, workers.length
22
19
  end
23
20
 
24
21
  def test_scheduled
@@ -0,0 +1,22 @@
1
+ require_relative 'helper'
2
+
3
+ class TestSubclassTracking < Sidetiq::TestCase
4
+ class Foo
5
+ extend Sidetiq::SubclassTracking
6
+ end
7
+
8
+ class Bar < Foo
9
+ end
10
+
11
+ class Baz < Bar
12
+ end
13
+
14
+ def test_subclasses_non_recursive
15
+ assert_equal [Bar], Foo.subclasses
16
+ end
17
+
18
+ def test_subclasses_recursive
19
+ assert_equal [Bar, Baz], Foo.subclasses(true)
20
+ end
21
+ end
22
+
data/test/test_web.rb CHANGED
@@ -14,6 +14,7 @@ class TestWeb < Sidetiq::TestCase
14
14
  def setup
15
15
  super
16
16
  ScheduledWorker.jobs.clear
17
+ Sidetiq.stubs(:workers).returns([ScheduledWorker])
17
18
  end
18
19
 
19
20
  def test_home_tab
@@ -27,7 +28,7 @@ class TestWeb < Sidetiq::TestCase
27
28
  get '/sidetiq'
28
29
  assert_equal 200, last_response.status
29
30
 
30
- clock.schedules.each do |worker, schedule|
31
+ Sidetiq.workers.each do |worker|
31
32
  assert_match /#{worker.name}/, last_response.body
32
33
  assert_match /#{worker.get_sidekiq_options['queue']}/, last_response.body
33
34
  end
@@ -46,7 +47,7 @@ class TestWeb < Sidetiq::TestCase
46
47
  def test_schedule_page
47
48
  get "/sidetiq/ScheduledWorker/schedule"
48
49
  assert_equal 200, last_response.status
49
- schedule = clock.schedules[ScheduledWorker]
50
+ schedule = ScheduledWorker.schedule
50
51
 
51
52
  schedule.recurrence_rules.each do |rule|
52
53
  assert_match /#{rule.to_s}/, last_response.body
data/test/test_worker.rb CHANGED
@@ -24,6 +24,6 @@ class TestWorker < Sidetiq::TestCase
24
24
  end
25
25
 
26
26
  def test_options
27
- assert Sidetiq.schedules[BackfillWorker].backfill?
27
+ assert BackfillWorker.schedule.backfill?
28
28
  end
29
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidetiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0.rc2
4
+ version: 0.4.0.rc3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tobias Svensson
@@ -184,6 +184,7 @@ files:
184
184
  - lib/sidetiq/middleware/history.rb
185
185
  - lib/sidetiq/schedulable.rb
186
186
  - lib/sidetiq/schedule.rb
187
+ - lib/sidetiq/subclass_tracking.rb
187
188
  - lib/sidetiq/supervisor.rb
188
189
  - lib/sidetiq/version.rb
189
190
  - lib/sidetiq/views/_home_nav.erb
@@ -212,6 +213,7 @@ files:
212
213
  - test/test_lock_redis.rb
213
214
  - test/test_schedule.rb
214
215
  - test/test_sidetiq.rb
216
+ - test/test_subclass_tracking.rb
215
217
  - test/test_version.rb
216
218
  - test/test_watcher.rb
217
219
  - test/test_web.rb
@@ -256,6 +258,7 @@ test_files:
256
258
  - test/test_lock_redis.rb
257
259
  - test/test_schedule.rb
258
260
  - test/test_sidetiq.rb
261
+ - test/test_subclass_tracking.rb
259
262
  - test/test_version.rb
260
263
  - test/test_watcher.rb
261
264
  - test/test_web.rb