geary 0.0.1

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.
@@ -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