pigeon 0.3.0 → 0.4.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.
data/lib/pigeon.rb CHANGED
@@ -1,12 +1,20 @@
1
1
  module Pigeon
2
2
  # == Autoloads ============================================================
3
3
 
4
+ autoload(:Backlog, 'pigeon/backlog')
5
+ autoload(:Dispatcher, 'pigeon/dispatcher')
4
6
  autoload(:Engine, 'pigeon/engine')
5
7
  autoload(:Launcher, 'pigeon/launcher')
6
8
  autoload(:OptionAccessor, 'pigeon/option_accessor')
7
9
  autoload(:Pidfile, 'pigeon/pidfile')
10
+ autoload(:Processor, 'pigeon/processor')
8
11
  autoload(:Queue, 'pigeon/queue')
12
+ autoload(:Scheduler, 'pigeon/scheduler')
13
+ autoload(:SortedArray, 'pigeon/sorted_array')
9
14
  autoload(:Support, 'pigeon/support')
15
+ autoload(:Task, 'pigeon/task')
10
16
  end
11
17
 
12
- require 'pigeon/logger'
18
+ # NOTE: This file needs to be directly loaded due to some kind of peculiar
19
+ # issue where requiring it later causes a run-time Interrupt exception.
20
+ require 'pigeon/logger'
data/pigeon.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{pigeon}
8
- s.version = "0.3.0"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["tadman"]
12
- s.date = %q{2010-10-19}
12
+ s.date = %q{2010-11-09}
13
13
  s.default_executable = %q{launcher.example}
14
14
  s.description = %q{Pigeon is a simple way to get started building an EventMachine engine that's intended to run as a background job.}
15
15
  s.email = %q{github@tadman.ca}
@@ -27,20 +27,31 @@ Gem::Specification.new do |s|
27
27
  "VERSION",
28
28
  "bin/launcher.example",
29
29
  "lib/pigeon.rb",
30
+ "lib/pigeon/dispatcher.rb",
30
31
  "lib/pigeon/engine.rb",
31
32
  "lib/pigeon/launcher.rb",
32
33
  "lib/pigeon/logger.rb",
33
34
  "lib/pigeon/option_accessor.rb",
34
35
  "lib/pigeon/pidfile.rb",
36
+ "lib/pigeon/processor.rb",
35
37
  "lib/pigeon/queue.rb",
38
+ "lib/pigeon/scheduler.rb",
39
+ "lib/pigeon/sorted_array.rb",
36
40
  "lib/pigeon/support.rb",
41
+ "lib/pigeon/task.rb",
37
42
  "pigeon.gemspec",
38
43
  "test/helper.rb",
39
- "test/test_pigeon.rb",
40
- "test/test_pigeon_engine.rb",
41
- "test/test_pigeon_launcher.rb",
42
- "test/test_pigeon_option_accessor.rb",
43
- "test/test_pigeon_queue.rb"
44
+ "test/unit/pigeon_backlog_test.rb",
45
+ "test/unit/pigeon_dispatcher_test.rb",
46
+ "test/unit/pigeon_engine_test.rb",
47
+ "test/unit/pigeon_launcher_test.rb",
48
+ "test/unit/pigeon_option_accessor_test.rb",
49
+ "test/unit/pigeon_processor_test.rb",
50
+ "test/unit/pigeon_queue_test.rb",
51
+ "test/unit/pigeon_scheduler_test.rb",
52
+ "test/unit/pigeon_sorted_array_test.rb",
53
+ "test/unit/pigeon_task_test.rb",
54
+ "test/unit/pigeon_test.rb"
44
55
  ]
45
56
  s.homepage = %q{http://github.com/tadman/pigeon}
46
57
  s.rdoc_options = ["--charset=UTF-8"]
@@ -49,11 +60,17 @@ Gem::Specification.new do |s|
49
60
  s.summary = %q{Simple daemonized EventMachine engine framework with plug-in support}
50
61
  s.test_files = [
51
62
  "test/helper.rb",
52
- "test/test_pigeon.rb",
53
- "test/test_pigeon_engine.rb",
54
- "test/test_pigeon_launcher.rb",
55
- "test/test_pigeon_option_accessor.rb",
56
- "test/test_pigeon_queue.rb"
63
+ "test/unit/pigeon_backlog_test.rb",
64
+ "test/unit/pigeon_dispatcher_test.rb",
65
+ "test/unit/pigeon_engine_test.rb",
66
+ "test/unit/pigeon_launcher_test.rb",
67
+ "test/unit/pigeon_option_accessor_test.rb",
68
+ "test/unit/pigeon_processor_test.rb",
69
+ "test/unit/pigeon_queue_test.rb",
70
+ "test/unit/pigeon_scheduler_test.rb",
71
+ "test/unit/pigeon_sorted_array_test.rb",
72
+ "test/unit/pigeon_task_test.rb",
73
+ "test/unit/pigeon_test.rb"
57
74
  ]
58
75
 
59
76
  if s.respond_to? :specification_version then
data/test/helper.rb CHANGED
@@ -4,6 +4,8 @@ require 'test/unit'
4
4
  $LOAD_PATH.unshift(File.expand_path(*%w[ .. lib ]), File.dirname(__FILE__))
5
5
  $LOAD_PATH.unshift(File.dirname(__FILE__))
6
6
 
7
+ require 'timeout'
8
+
7
9
  require 'rubygems'
8
10
 
9
11
  if (Gem.available?('eventmachine'))
@@ -15,4 +17,21 @@ end
15
17
  require 'pigeon'
16
18
 
17
19
  class Test::Unit::TestCase
20
+ def assert_timeout(time, message = nil, &block)
21
+ Timeout::timeout(time, &block)
22
+ rescue Timeout::Error
23
+ flunk(message || 'assert_timeout timed out')
24
+ end
25
+
26
+ def assert_eventually(time = nil, message = nil, &block)
27
+ start_time = Time.now.to_i
28
+
29
+ while (!block.call)
30
+ select(nil, nil, nil, 0.1)
31
+
32
+ if (time and (Time.now.to_i - start_time > time))
33
+ flunk(message || 'assert_eventually timed out')
34
+ end
35
+ end
36
+ end
18
37
  end
@@ -0,0 +1,25 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonQueueTest < Test::Unit::TestCase
4
+ def test_empty_queue
5
+ queue = Pigeon::Queue.new
6
+
7
+ assert queue.empty?
8
+ assert_equal 0, queue.length
9
+
10
+ assert_equal nil, queue.pop
11
+ end
12
+
13
+ def test_queue_cycling
14
+ queue = Pigeon::Queue.new
15
+
16
+ task = Pigeon::Task.new
17
+
18
+ queue << task
19
+
20
+ assert_equal 1, queue.length
21
+ assert !queue.empty?
22
+
23
+ found_task = queue.pop
24
+ end
25
+ end
@@ -0,0 +1,62 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonDispatcherTest < Test::Unit::TestCase
4
+ def test_routine_dispatching
5
+ dispatcher = Pigeon::Dispatcher.new
6
+
7
+ assert_equal Pigeon::Dispatcher.thread_limit, dispatcher.thread_limit
8
+
9
+ checks = { }
10
+
11
+ count = 100
12
+
13
+ count.times do |n|
14
+ dispatcher.perform do
15
+ x = 0
16
+
17
+ 1000.times do
18
+ dispatcher.perform do
19
+ x += 1
20
+ end
21
+ end
22
+
23
+ checks[n] = x
24
+ end
25
+ end
26
+
27
+ assert_timeout(10) do
28
+ dispatcher.wait!
29
+ end
30
+
31
+ assert_equal 0, dispatcher.backlog_size
32
+ assert_equal 0, dispatcher.thread_count
33
+ assert dispatcher.empty?
34
+ assert_equal [ ], dispatcher.exceptions
35
+ assert !dispatcher.exceptions?
36
+
37
+ assert_equal (0..count - 1).to_a, checks.keys.sort
38
+ end
39
+
40
+ def test_dispatch_variants
41
+ dispatcher = Pigeon::Dispatcher.new
42
+
43
+ result = [ ]
44
+
45
+ dispatcher.perform do
46
+ result[0] = :a
47
+ end
48
+
49
+ dispatcher.perform(:b) do |b|
50
+ result[1] = b
51
+ end
52
+
53
+ dispatcher.perform(:c, :d) do |c, d|
54
+ result[2] = c
55
+ result[3] = d
56
+ end
57
+
58
+ dispatcher.wait!
59
+
60
+ assert_equal [ :a, :b, :c, :d ], result
61
+ end
62
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
2
 
3
3
  module TestModule
4
4
  def self.included(engine)
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
2
 
3
3
  class PigeonLauncherTest < Test::Unit::TestCase
4
4
  def test_default_launcher
@@ -1,4 +1,4 @@
1
- require File.expand_path(File.join(*%w[ helper ]), File.dirname(__FILE__))
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
2
 
3
3
  class OptionClass
4
4
  extend Pigeon::OptionAccessor
@@ -0,0 +1,94 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonProcessorTest < Test::Unit::TestCase
4
+ class TaggedTask < Pigeon::Task
5
+ attr_accessor :tag
6
+
7
+ def initialize(engine, tag)
8
+ super(engine)
9
+ @tag = tag
10
+ end
11
+
12
+ def inspect
13
+ "<#{@tag}>"
14
+ end
15
+ end
16
+
17
+ def engine
18
+ @engine ||= Pigeon::Engine.new
19
+ end
20
+
21
+ def test_empty_processor
22
+ queue = Pigeon::Queue.new
23
+
24
+ processor = Pigeon::Processor.new(queue)
25
+
26
+ assert_equal false, processor.task?
27
+
28
+ assert_equal true, processor.accept?(Pigeon::Task.new(engine))
29
+
30
+ assert processor.id
31
+ end
32
+
33
+ def test_simple_filter
34
+ queue = Pigeon::Queue.new
35
+
36
+ processor = Pigeon::Processor.new(queue) do |task|
37
+ (task.tag % 2) == 1
38
+ end
39
+
40
+ assert_equal false, processor.task?
41
+
42
+ queue << TaggedTask.new(engine, 0)
43
+
44
+ assert_equal false, processor.task?
45
+ assert_equal 1, queue.length
46
+
47
+ queue << TaggedTask.new(engine, 1)
48
+
49
+ assert_equal true, processor.task?
50
+ assert_equal 1, queue.length
51
+
52
+ assert_eventually(5) do
53
+ !processor.task?
54
+ end
55
+
56
+ assert_equal 1, queue.length
57
+ end
58
+
59
+ def test_on_backlog
60
+ queue = Pigeon::Queue.new
61
+
62
+ 100.times do |n|
63
+ queue << TaggedTask.new(engine, n)
64
+ end
65
+
66
+ processor = Pigeon::Processor.new(queue)
67
+
68
+ assert_eventually(5) do
69
+ queue.empty?
70
+ end
71
+ end
72
+
73
+ def test_multiple_processors
74
+ queue = Pigeon::Queue.new
75
+ count = 10000
76
+
77
+ count.times do |n|
78
+ queue << TaggedTask.new(engine, n)
79
+ end
80
+
81
+ assert_equal count, queue.length
82
+
83
+ processors = (0..9).to_a.collect do
84
+ Pigeon::Processor.new(queue)
85
+ end
86
+
87
+ assert_eventually(10) do
88
+ queue.empty?
89
+ end
90
+
91
+ assert_equal 0, processors.select(&:task?).length
92
+ assert_equal 0, queue.length
93
+ end
94
+ end
@@ -0,0 +1,197 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonQueueTest < Test::Unit::TestCase
4
+ class TaggedTask < Pigeon::Task
5
+ attr_accessor :tag
6
+
7
+ def initialize(engine, tag)
8
+ super(engine)
9
+ @tag = tag
10
+ end
11
+
12
+ def inspect
13
+ "<#{@tag}>"
14
+ end
15
+ end
16
+
17
+ def engine
18
+ @engine ||= Pigeon::Engine.new
19
+ end
20
+
21
+ def test_empty_state
22
+ queue = Pigeon::Queue.new
23
+
24
+ assert_equal 0, queue.length
25
+ assert_equal true, queue.empty?
26
+
27
+ assert_equal nil, queue.pop
28
+ end
29
+
30
+ def test_cycling
31
+ queue = Pigeon::Queue.new
32
+
33
+ task = Pigeon::Task.new(engine)
34
+
35
+ assert_equal task, queue << task
36
+
37
+ assert queue.peek
38
+ assert_equal 1, queue.length
39
+ assert !queue.empty?
40
+
41
+ found_task = queue.pop
42
+
43
+ assert_equal task, found_task
44
+
45
+ assert_equal 0, queue.length
46
+ assert queue.empty?
47
+ end
48
+
49
+ def test_filtering
50
+ queue = Pigeon::Queue.new
51
+
52
+ tasks = (0..9).to_a.collect do |n|
53
+ queue << TaggedTask.new(engine, n)
54
+ end
55
+
56
+ assert_equal (0..9).to_a, tasks.to_a.collect(&:tag)
57
+
58
+ assert_equal tasks[0], queue.peek
59
+
60
+ selected_task = queue.peek do |task|
61
+ task.tag > 0
62
+ end
63
+
64
+ assert_equal tasks[1], selected_task
65
+
66
+ queue.filter(:over_7) do |task|
67
+ task.tag > 7
68
+ end
69
+
70
+ assert_equal tasks[8], queue.peek(:over_7)
71
+ assert_equal 2, queue.length(:over_7)
72
+
73
+ pulled_task = queue.pop(:over_7)
74
+
75
+ assert_equal 9, queue.length
76
+
77
+ assert_equal tasks[9], queue.peek(:over_7)
78
+ assert_equal 1, queue.length(:over_7)
79
+
80
+ queue.pop(:over_7)
81
+
82
+ assert_equal nil, queue.peek(:over_7)
83
+ assert_equal 0, queue.length(:over_7)
84
+ assert_equal true, queue.empty?(:over_7)
85
+
86
+ new_task = queue << TaggedTask.new(engine, 10)
87
+
88
+ assert_equal new_task, queue.peek(:over_7)
89
+ assert_equal 1, queue.length(:over_7)
90
+ assert_equal false, queue.empty?(:over_7)
91
+
92
+ queue.claim(new_task)
93
+
94
+ assert_equal nil, queue.peek(:over_7)
95
+ assert_equal 0, queue.length(:over_7)
96
+ assert_equal true, queue.empty?(:over_7)
97
+ end
98
+
99
+ def test_observe
100
+ queue = Pigeon::Queue.new
101
+
102
+ tasks = (0..9).to_a.collect do |n|
103
+ queue << TaggedTask.new(engine, n)
104
+ end
105
+
106
+ queue.filter(:odd) do |task|
107
+ task.tag % 2 == 1
108
+ end
109
+
110
+ assert_equal tasks[1], queue.peek(:odd)
111
+ assert_equal 5, queue.length(:odd)
112
+
113
+ assert_equal [ tasks[1], tasks[3], tasks[5], tasks[7], tasks[9] ], queue.pull(:odd)
114
+
115
+ assert_equal 5, queue.length
116
+ assert_equal 0, queue.length(:odd)
117
+
118
+ added_odd = nil
119
+
120
+ queue.observe(:odd) do |task|
121
+ added_odd = task
122
+ end
123
+
124
+ queue << TaggedTask.new(engine, 10)
125
+
126
+ assert_equal nil, added_odd
127
+
128
+ odd_1 = queue << TaggedTask.new(engine, 11)
129
+
130
+ assert_equal odd_1, added_odd
131
+
132
+ claimed_task = nil
133
+ has_run = false
134
+
135
+ queue.observe(:odd) do |task|
136
+ claimed_task = queue.claim(task)
137
+ has_run = true
138
+ end
139
+
140
+ # Observer callbacks are not triggered on existing data, only on new
141
+ # insertions.
142
+ assert_equal false, has_run
143
+ assert_equal nil, claimed_task
144
+ assert_equal 7, queue.length
145
+ assert_equal 1, queue.length(:odd)
146
+
147
+ queue << TaggedTask.new(engine, 12)
148
+
149
+ assert_equal nil, claimed_task
150
+ assert_equal 8, queue.length
151
+ assert_equal 1, queue.length(:odd)
152
+
153
+ odd_2 = queue << TaggedTask.new(engine, 13)
154
+
155
+ # Adding a task that matches the filter triggers the callback.
156
+ assert_equal odd_2, claimed_task
157
+ assert_equal true, has_run
158
+
159
+ # Clear out all of the odd entries.
160
+ queue.pull(:odd)
161
+
162
+ claimed_task = nil
163
+ has_run = false
164
+
165
+ queue << TaggedTask.new(engine, 14)
166
+
167
+ assert_equal nil, claimed_task
168
+ assert_equal false, has_run
169
+
170
+ odd_2 = queue << TaggedTask.new(engine, 15)
171
+
172
+ assert_equal odd_2, claimed_task
173
+ assert_equal true, has_run
174
+
175
+ assert_equal 8, queue.length
176
+ assert_equal 0, queue.length(:odd)
177
+ end
178
+
179
+ def test_can_add_during_observe
180
+ queue = Pigeon::Queue.new
181
+
182
+ queue.observe do |task|
183
+ if (task.tag < 10)
184
+ queue.claim(task)
185
+
186
+ queue << TaggedTask.new(engine, task.tag + 1)
187
+ end
188
+ end
189
+
190
+ queue << TaggedTask.new(engine, 0)
191
+
192
+ assert queue.peek
193
+ assert_equal 10, queue.peek.tag
194
+ assert_equal 1, queue.length
195
+ assert queue.peek
196
+ end
197
+ end
@@ -0,0 +1,70 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonSchedulerTest < Test::Unit::TestCase
4
+ class TaggedTask < Pigeon::Task
5
+ attr_accessor :tag
6
+
7
+ def initialize(engine, tag)
8
+ super(engine)
9
+ @tag = tag
10
+ end
11
+
12
+ def inspect
13
+ "<#{@tag}>"
14
+ end
15
+ end
16
+
17
+ def engine
18
+ @engine ||= Pigeon::Engine.new
19
+ end
20
+
21
+ def test_defaults
22
+ scheduler = Pigeon::Scheduler.new
23
+
24
+ assert_equal true, scheduler.default_queue.empty?
25
+ end
26
+
27
+ def test_queued
28
+ queue = Pigeon::Queue.new
29
+
30
+ scheduler = Pigeon::Scheduler.new(queue)
31
+
32
+ count = 1000
33
+
34
+ count.times do |n|
35
+ queue << TaggedTask.new(engine, n)
36
+ end
37
+
38
+ assert_eventually(5) do
39
+ queue.empty?
40
+ end
41
+
42
+ assert_equal 0, scheduler.processors.select(&:task?).length
43
+ assert_equal 0, queue.length
44
+ end
45
+
46
+ def test_add
47
+ queue = Pigeon::Queue.new
48
+
49
+ scheduler = Pigeon::Scheduler.new(queue)
50
+
51
+ count = 1000
52
+
53
+ backlog = [ ]
54
+ count.times do |n|
55
+ scheduler.add(TaggedTask.new(engine, n * 2 + 1))
56
+ backlog << TaggedTask.new(engine, n * 2)
57
+ end
58
+
59
+ scheduler.add(backlog)
60
+
61
+ assert !queue.empty?
62
+
63
+ assert_eventually(5) do
64
+ queue.empty?
65
+ end
66
+
67
+ assert_equal 0, scheduler.processors.select(&:task?).length
68
+ assert_equal 0, queue.length
69
+ end
70
+ end
@@ -0,0 +1,52 @@
1
+ require File.expand_path(File.join(*%w[ .. helper ]), File.dirname(__FILE__))
2
+
3
+ class PigeonSortedArrayTest < Test::Unit::TestCase
4
+ def test_empty_state
5
+ array = Pigeon::SortedArray.new
6
+
7
+ assert array.empty?
8
+ assert array.is_a?(Array)
9
+ assert_equal [ ], array
10
+
11
+ dup = array.dup
12
+
13
+ assert_equal Pigeon::SortedArray, dup.class
14
+
15
+ combined = (array + array)
16
+
17
+ assert_equal [ ], combined
18
+ assert_equal Pigeon::SortedArray, combined.class
19
+ end
20
+
21
+ def test_does_sorting
22
+ array = Pigeon::SortedArray.new
23
+
24
+ test_array = (0..100).to_a.reverse
25
+
26
+ array += test_array
27
+
28
+ assert_equal (0..100).to_a, array
29
+ end
30
+
31
+ def test_does_sorting_with_insertion_in_order
32
+ array = Pigeon::SortedArray.new
33
+
34
+ 10.times do |n|
35
+ array << n.to_f
36
+ end
37
+
38
+ assert_equal (0..9).to_a.collect(&:to_f), array
39
+ end
40
+
41
+ def test_does_sorting_with_insertion_random_order
42
+ array = Pigeon::SortedArray.new
43
+
44
+ srand
45
+ (0..10000).to_a.sort_by { rand }.each do |i|
46
+ array << i
47
+ end
48
+
49
+ assert_equal (0..10000).to_a, array
50
+ assert_equal 10001, array.length
51
+ end
52
+ end