pigeon 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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