geary 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ require 'gearman/client'
2
+ require 'support/fake_server'
3
+ require 'uri'
4
+
5
+ module Gearman
6
+ describe Client do
7
+
8
+ let!(:address) { URI('gearman://127.0.0.1:4730') }
9
+ let!(:gearmand) { FakeServer.new(address) }
10
+
11
+ before do
12
+ gearmand.async.run
13
+ gearmand.wait :accept
14
+ end
15
+
16
+ after { gearmand.shutdown }
17
+
18
+ it 'submits background jobs' do
19
+ gearmand.respond_with(Packet::JOB_CREATED.new(['handle']))
20
+
21
+ expected_packet = Packet::SUBMIT_JOB_BG.new(
22
+ function_name: 'super_ability',
23
+ unique_id: 'UUID',
24
+ data: 'data'
25
+ )
26
+
27
+ client = Client.new(address)
28
+ client.generate_unique_id_with -> { 'UUID' }
29
+
30
+ client.submit_job_bg('super_ability', 'data')
31
+
32
+ expect(gearmand.packets_read.last).to eql(expected_packet)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ require 'gearman/connection'
2
+ require 'support/fake_server'
3
+ require 'support/without_logging'
4
+ require 'uri'
5
+
6
+ module Gearman
7
+
8
+ describe Connection do
9
+ include WithoutLogging
10
+
11
+ let!(:address) { URI('gearman://127.0.0.1:4730') }
12
+ let!(:server) { FakeServer.new(address) }
13
+
14
+ before do
15
+ server.async.run
16
+ server.wait :accept
17
+ end
18
+
19
+ after do
20
+ server.shutdown
21
+ end
22
+
23
+ it 'can write packets to a socket' do
24
+ connection = Connection.new(address)
25
+ connection.write(Gearman::Packet::WORK_COMPLETE.new(['*', '*']))
26
+
27
+ expect(server.packets_read.last).
28
+ to eql(Gearman::Packet::WORK_COMPLETE.new(['*', '*']))
29
+ end
30
+
31
+ it 'can read packets from a socket' do
32
+ server.respond_with(Gearman::Packet::NO_JOB.new)
33
+
34
+ connection = Connection.new(address)
35
+ connection.write(Gearman::Packet::GRAB_JOB.new)
36
+
37
+ expect(connection.next).to eql(Gearman::Packet::NO_JOB.new)
38
+ end
39
+
40
+ it 'can specify that it expects only certain types of packets' do
41
+ without_logging do
42
+ server.respond_with(Gearman::Packet::JOB_ASSIGN.new([1] * 3))
43
+
44
+ connection = Connection.new(address)
45
+ connection.write(Gearman::Packet::GRAB_JOB.new)
46
+
47
+ expect do
48
+ connection.next(Gearman::Packet::NO_JOB)
49
+ end.to raise_error(Connection::UnexpectedPacketError)
50
+ end
51
+ end
52
+
53
+ it 'will raise a ServerError if it reads an error packet' do
54
+ without_logging do
55
+ server.respond_with(Gearman::Packet::ERROR.new(["E", "T"]))
56
+
57
+ connection = Connection.new(address)
58
+ connection.write(Gearman::Packet::GRAB_JOB.new)
59
+
60
+ expect do
61
+ connection.next(Gearman::Packet::NO_JOB)
62
+ end.to raise_error(Connection::ServerError)
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ require 'gearman/packet/sugar'
2
+
3
+ module Gearman
4
+ module Packet
5
+
6
+ describe Sugar do
7
+
8
+ it 'allows classes to create initializers which accept options' do
9
+ class_ = Class.new { extend Sugar ; takes(:foo, :bar) }
10
+ object = class_.new(foo: 'foo', bar: 'bar')
11
+
12
+ expect(object.foo).to eql('foo')
13
+ expect(object.bar).to eql('bar')
14
+ expect(object.arguments).to eql(['foo', 'bar'])
15
+ end
16
+
17
+ it 'allows classes to create initializers which accept positional arguments' do
18
+ class_ = Class.new { extend Sugar ; takes(:foo, :bar) }
19
+ object = class_.new(['foo', 'bar'])
20
+
21
+ expect(object.foo).to eql('foo')
22
+ expect(object.bar).to eql('bar')
23
+ expect(object.arguments).to eql(['foo', 'bar'])
24
+ end
25
+
26
+ it 'raises an ArgumentError if given something other than a Hash or an Array' do
27
+ class_ = Class.new { extend Sugar ; takes(:foo, :bar) }
28
+
29
+ expect do
30
+ object = class_.new(1)
31
+ end.to raise_error(ArgumentError)
32
+ end
33
+
34
+ it 'allows classes to set "numbers" for their instances' do
35
+ class_ = Class.new { extend Sugar ; number 1 }
36
+ object = class_.new
37
+
38
+ expect(object.number).to eql(1)
39
+ end
40
+
41
+ it 'can create new packet types with ease' do
42
+ type = Sugar.type 'CanDo', takes: [:function_name], number: 1
43
+
44
+ expect(type.new(['foo']).function_name).to eql('foo')
45
+ expect(type.new(function_name: 'foo').function_name).to eql('foo')
46
+ expect(type.new(function_name: 'foo').number).to eql(1)
47
+ end
48
+
49
+ it 'creates packets with value equality' do
50
+ type = Sugar.type 'CanDo', takes: [:function_name], number: 1
51
+
52
+ expect(type.new(['foo'])).to eql(type.new(['foo']))
53
+ end
54
+
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ require 'gearman/packet'
2
+
3
+ module Gearman
4
+ describe Packet do
5
+
6
+ before do
7
+ Packet::Repository.new
8
+ end
9
+
10
+ it 'contains a bunch of packet types' do
11
+ %w(CAN_DO PRE_SLEEP NOOP GRAB_JOB NO_JOB JOB_ASSIGN
12
+ WORK_COMPLETE WORK_EXCEPTION).each do |type|
13
+ type = Gearman::Packet.const_get(type)
14
+ arguments = type.const_get('ARGUMENTS').map { 'foo ' }
15
+
16
+ expect do
17
+ type.new(arguments)
18
+ end.to_not raise_error
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,68 @@
1
+ require 'gearman/worker'
2
+ require 'support/actor_double'
3
+
4
+ module Gearman
5
+
6
+ class FakeConnection
7
+ include Celluloid
8
+ end
9
+
10
+ describe Worker do
11
+ include ActorDouble
12
+
13
+ let!(:connection) { actor_double }
14
+
15
+ it 'expects a NOOP after it sends PRE_SLEEP' do
16
+ connection.stub(:write)
17
+ connection.should_receive(:next).with(Packet::NOOP)
18
+
19
+ worker = Worker.new('gearman://localhost:4730')
20
+ worker.configure_connection ->(address) { connection }
21
+
22
+ worker.pre_sleep
23
+ end
24
+
25
+ it 'expects either a JOB_ASSIGN or a NO_JOB when it grabs a job' do
26
+ connection.stub(:write)
27
+ connection.should_receive(:next).with(Packet::JOB_ASSIGN, Packet::NO_JOB)
28
+
29
+ worker = Worker.new('gearman://localhost:4730')
30
+ worker.configure_connection ->(address) { connection }
31
+
32
+ worker.grab_job
33
+ end
34
+
35
+ it 'sends WORK_EXCEPTION' do
36
+ work_exception = Packet::WORK_EXCEPTION.new(handle: 'h', data: 'd')
37
+ connection.should_receive(:write).with(work_exception)
38
+
39
+ worker = Worker.new('gearman://localhost:4730')
40
+ worker.configure_connection ->(address) { connection }
41
+
42
+ worker.work_exception('h', 'd')
43
+ end
44
+
45
+ it 'sends WORK_COMPLETE' do
46
+ work_complete = Packet::WORK_COMPLETE.new(handle: 'h', data: 'd')
47
+ connection.should_receive(:write).with(work_complete)
48
+
49
+ worker = Worker.new('gearman://localhost:4730')
50
+ worker.configure_connection ->(address) { connection }
51
+
52
+ worker.work_complete('h', 'd')
53
+ end
54
+
55
+ it 'sends CAN_DO' do
56
+ can_do = Packet::CAN_DO.new(function_name: 'ability')
57
+
58
+ connection.should_receive(:write).with(can_do)
59
+
60
+ worker = Worker.new('gearman://localhost:4730')
61
+ worker.configure_connection ->(address) { connection }
62
+
63
+ worker.can_do('ability')
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,40 @@
1
+ require 'geary/cli'
2
+
3
+ require 'thread'
4
+ require 'timeout'
5
+
6
+ module Geary
7
+ describe CLI do
8
+
9
+ it 'shuts down when sent TERM' do
10
+ kernel = double('kernel')
11
+ kernel.should_receive(:exit)
12
+ argv = ['-c 0']
13
+ cli = CLI.new(argv, STDOUT, STDERR, kernel)
14
+
15
+ t = Thread.new { cli.execute! }
16
+ t.abort_on_exception = true
17
+
18
+ cli.external_signal_queue.puts('TERM')
19
+
20
+ Timeout.timeout(1, StandardError) { t.value } rescue nil
21
+ end
22
+
23
+ it 'shuts down when sent INT' do
24
+ kernel = double('kernel')
25
+ kernel.should_receive(:exit)
26
+
27
+ argv = ['-c 0']
28
+ cli = CLI.new(argv, STDOUT, STDERR, kernel)
29
+
30
+ t = Thread.new { cli.execute! }
31
+ t.abort_on_exception = true
32
+
33
+ IO.select([], [cli.external_signal_queue])
34
+ cli.external_signal_queue.puts('INT')
35
+
36
+ Timeout.timeout(1, StandardError) { t.value } rescue nil
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,123 @@
1
+ require 'gearmand_control'
2
+
3
+ require 'geary/configuration'
4
+ require 'geary/manager'
5
+
6
+ require 'support/fake_performer'
7
+ require 'support/with_tolerance'
8
+ require 'support/without_logging'
9
+
10
+ module Geary
11
+
12
+ describe Manager do
13
+ include WithTolerance
14
+ include WithoutLogging
15
+
16
+ let(:configuration) do
17
+ configuration = Configuration.new(
18
+ server_addresses: ['gearman://localhost:4730'],
19
+ concurrency: 2
20
+ )
21
+ end
22
+
23
+ describe 'starting the manager' do
24
+
25
+ it 'establishes a link to managed performers' do
26
+ manager = Manager.new(configuration: configuration,
27
+ performer_type: FakePerformer)
28
+ manager.start
29
+
30
+ expect(manager.links.count).to eql(2)
31
+ end
32
+
33
+ it 'starts each managed performer' do
34
+ manager = Manager.new(configuration: configuration,
35
+ performer_type: FakePerformer)
36
+ manager.start
37
+
38
+ expect(manager.links.all?(&:started?)).to be_true
39
+ expect(manager.links.map(&:server_address).uniq).
40
+ to eql(configuration.server_addresses)
41
+ end
42
+
43
+ end
44
+
45
+ describe 'performer supervision' do
46
+
47
+ it 'restarts performers when they die' do
48
+ without_logging do
49
+ manager = Manager.new(configuration: configuration,
50
+ performer_type: FakePerformer)
51
+ manager.start
52
+
53
+ imminently_dead_performers = manager.performers
54
+ imminently_dead_performers.map(&:async).each(&:die)
55
+
56
+ with_tolerance do
57
+ expect(imminently_dead_performers.count(&:alive?)).to eql(0)
58
+ end
59
+
60
+ with_tolerance do
61
+ expect(manager.links.count).to eql(2)
62
+ end
63
+ end
64
+ end
65
+
66
+ it 'forgets performers if they die without a reason' do
67
+ without_logging do
68
+ manager = Manager.new(configuration: configuration,
69
+ performer_type: FakePerformer)
70
+ manager.start
71
+
72
+ forgettable_performers = manager.performers
73
+ forgettable_performers.map(&:async).each(&:die_quietly)
74
+
75
+ with_tolerance do
76
+ expect(forgettable_performers.count(&:alive?)).to eql(0)
77
+ end
78
+
79
+ with_tolerance do
80
+ expect(manager.links.count).to eql(0)
81
+ end
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ describe 'ceasing management' do
88
+
89
+ it 'terminates linked performers' do
90
+ manager = Manager.new(configuration: configuration,
91
+ performer_type: FakePerformer)
92
+ manager.start
93
+
94
+ performers = manager.performers
95
+
96
+ expect do
97
+ manager.stop
98
+ end.to change { performers.count(&:alive?) }.from(2).to(0)
99
+ end
100
+
101
+ it 'unlinks linked performers' do
102
+ manager = Manager.new(configuration: configuration,
103
+ performer_type: FakePerformer)
104
+ manager.start
105
+
106
+ expect do
107
+ manager.stop
108
+ end.to change { manager.links.count }.from(2).to(0)
109
+ end
110
+
111
+ it 'signals that it has stopped' do
112
+ manager = Manager.new(configuration: configuration,
113
+ performer_type: FakePerformer)
114
+ manager.async.start
115
+ manager.async.stop
116
+
117
+ expect(manager.wait :done).to be_nil
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,17 @@
1
+ require 'geary/option_parser'
2
+
3
+ module Geary
4
+ describe OptionParser do
5
+
6
+ it 'understands comma-delimited servers to mean multiple servers' do
7
+ args = ['-s', 'gearman://localhost:4730,gearman://localhost:4731']
8
+ parser = OptionParser.new
9
+
10
+ configuration = parser.parse(args)
11
+
12
+ expect(configuration.server_addresses.map(&:to_s)).
13
+ to eql(['gearman://localhost:4730', 'gearman://localhost:4731'])
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,128 @@
1
+ require 'geary/performer'
2
+ require 'support/actor_double'
3
+ require 'support/without_logging'
4
+ require 'uri'
5
+
6
+ module Geary
7
+ describe Performer do
8
+ include ActorDouble
9
+ include WithoutLogging
10
+
11
+ let(:address) do
12
+ URI('gearman://localhost:4730')
13
+ end
14
+
15
+ let(:gearman) { actor_double }
16
+
17
+ it 'registers its ability with Gearman' do
18
+ gearman.stub(:grab_job)
19
+ gearman.should_receive(:can_do)
20
+
21
+ performer = Performer.new(address)
22
+ performer.configure_connection ->(address) { gearman }
23
+
24
+ performer.start
25
+ end
26
+
27
+ it 'tries to pop a job off the queue' do
28
+ gearman.stub(:can_do)
29
+ gearman.should_receive(:grab_job)
30
+
31
+ performer = Performer.new(address)
32
+ performer.configure_connection ->(address) { gearman }
33
+
34
+ performer.start
35
+ end
36
+
37
+ it 'sleeps if it gets a NO_JOB' do
38
+ gearman.stub(:can_do)
39
+ gearman.stub(:grab_job).and_return(Gearman::Packet::NO_JOB.new, nil)
40
+ gearman.should_receive(:pre_sleep)
41
+
42
+ performer = Performer.new(address)
43
+ performer.configure_connection ->(address) { gearman }
44
+
45
+ performer.start
46
+ end
47
+
48
+ it 'performs the job if it gets a JOB_ASSIGN' do
49
+ worker_class = Class.new { }
50
+ worker = worker_class.any_instance
51
+
52
+ Object.const_set('A', worker_class)
53
+
54
+ job = JSON.dump({
55
+ class: 'A',
56
+ args: ['a']
57
+ })
58
+
59
+ job_assign = Gearman::Packet::JOB_ASSIGN.new(['h', 'f', job])
60
+
61
+ gearman.stub(:can_do)
62
+ gearman.stub(:grab_job).and_return(job_assign, nil)
63
+ gearman.stub(:work_complete)
64
+
65
+ worker.should_receive(:perform).with('a')
66
+
67
+ performer = Performer.new(address)
68
+ performer.configure_connection ->(address) { gearman }
69
+
70
+ performer.start
71
+ end
72
+
73
+ it 'sends the result of a job to Gearman' do
74
+ worker_class = Class.new { }
75
+ worker = worker_class.any_instance
76
+ worker.stub(:perform) { 'result' }
77
+
78
+ Object.const_set('B', worker_class)
79
+
80
+ job = JSON.dump({
81
+ class: 'B',
82
+ args: ['a']
83
+ })
84
+
85
+ async_proxy = double('gearman.async')
86
+ job_assign = Gearman::Packet::JOB_ASSIGN.new(['h', 'f', job])
87
+
88
+ gearman.stub(:can_do)
89
+ gearman.stub(:grab_job).and_return(job_assign, nil)
90
+ gearman.stub(:async) { async_proxy }
91
+ async_proxy.should_receive(:work_complete).with('h', 'result')
92
+
93
+ performer = Performer.new(address)
94
+ performer.configure_connection ->(address) { gearman }
95
+
96
+ performer.start
97
+ end
98
+
99
+ it 'sends a WORK_EXCEPTION to Gearman if the job raises' do
100
+ worker_class = Class.new { }
101
+ worker = worker_class.any_instance
102
+ worker.stub(:perform).and_raise RuntimeError, "ack!"
103
+
104
+ Object.const_set('C', worker_class)
105
+
106
+ job = JSON.dump({
107
+ class: 'C',
108
+ args: ['a']
109
+ })
110
+
111
+ async_proxy = double('gearman.async')
112
+ job_assign = Gearman::Packet::JOB_ASSIGN.new(['h', 'f', job])
113
+
114
+ gearman.stub(:can_do)
115
+ gearman.stub(:grab_job).and_return(job_assign, nil)
116
+ gearman.stub(:async) { async_proxy }
117
+ async_proxy.should_receive(:work_exception).with('h', 'ack!')
118
+
119
+ performer = Performer.new(address)
120
+ performer.configure_connection ->(address) { gearman }
121
+
122
+ performer.start
123
+ end
124
+
125
+ it 'can repair its connection to Gearman'
126
+
127
+ end
128
+ end