cgminer_api_client 0.2.6
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 +7 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/.whitesource +8 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +188 -0
- data/Rakefile +7 -0
- data/bin/cgminer_api_client +22 -0
- data/cgminer_api_client.gemspec +20 -0
- data/config/miners.yml.example +2 -0
- data/lib/cgminer_api_client.rb +40 -0
- data/lib/cgminer_api_client/miner.rb +103 -0
- data/lib/cgminer_api_client/miner/commands.rb +201 -0
- data/lib/cgminer_api_client/miner_pool.rb +68 -0
- data/lib/cgminer_api_client/socket_with_timeout.rb +30 -0
- data/lib/cgminer_api_client/version.rb +3 -0
- data/spec/cgminer_api_client/miner/commands_spec.rb +501 -0
- data/spec/cgminer_api_client/miner_pool_spec.rb +134 -0
- data/spec/cgminer_api_client/miner_spec.rb +296 -0
- data/spec/cgminer_api_client_spec.rb +41 -0
- data/spec/spec_helper.rb +25 -0
- metadata +71 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CgminerApiClient::MinerPool do
|
4
|
+
let(:mock_miner) { instance_double('CgminerApiClient::Miner') }
|
5
|
+
let(:host) { '127.0.0.1' }
|
6
|
+
let(:port) { 1234 }
|
7
|
+
let(:timeout) { 10 }
|
8
|
+
let(:mock_miner_from_yaml) { double('miner_from_yaml', :[] => {'host' => host, 'port' => port, 'timeout' => timeout} ) }
|
9
|
+
let(:instance) { CgminerApiClient::MinerPool.new }
|
10
|
+
|
11
|
+
before do
|
12
|
+
allow(CgminerApiClient::Miner).to receive(:new).and_return(mock_miner)
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'attributes' do
|
16
|
+
context '@miners' do
|
17
|
+
before do
|
18
|
+
allow(File).to receive(:exist?).with('config/miners.yml').and_return(true)
|
19
|
+
allow_any_instance_of(CgminerApiClient::MinerPool).to receive(:load_miners!).and_return(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should allow setting and getting' do
|
23
|
+
instance.miners = :foo
|
24
|
+
expect(instance.miners).to eq :foo
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context '#initialize' do
|
30
|
+
it 'should load_miners!' do
|
31
|
+
expect_any_instance_of(CgminerApiClient::MinerPool).to receive(:load_miners!)
|
32
|
+
instance
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context '#available_miners' do
|
37
|
+
it 'should not include unavailable miners'
|
38
|
+
it 'should include available miners'
|
39
|
+
end
|
40
|
+
|
41
|
+
context '#query' do
|
42
|
+
before do
|
43
|
+
allow(File).to receive(:exist?).with('config/miners.yml').and_return(true)
|
44
|
+
allow_any_instance_of(CgminerApiClient::MinerPool).to receive(:load_miners!).and_return(true)
|
45
|
+
allow(instance).to receive(:load_miners!).and_return(true)
|
46
|
+
instance.instance_variable_set(:@miners, [mock_miner])
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should run provided query on each miner' do
|
50
|
+
expect(mock_miner).to receive(:query).with(:foo)
|
51
|
+
instance.query(:foo)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should pass parameters' do
|
55
|
+
expect(mock_miner).to receive(:query).with(:foo, :parameters)
|
56
|
+
instance.query(:foo, :parameters)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'should return an array' do
|
60
|
+
allow(mock_miner).to receive(:query).with(:foo)
|
61
|
+
expect(instance.query(:foo)).to be_kind_of(Array)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context '#method_missing' do
|
66
|
+
before do
|
67
|
+
allow(File).to receive(:exist?).with('config/miners.yml').and_return(true)
|
68
|
+
allow_any_instance_of(CgminerApiClient::MinerPool).to receive(:load_miners!).and_return(true)
|
69
|
+
allow(instance).to receive(:query).and_return(true)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should query each miner with the method name' do
|
73
|
+
expect(instance).to receive(:query).with(:foo).and_return(true)
|
74
|
+
instance.method_missing(:foo)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should pass arguments' do
|
78
|
+
expect(instance).to receive(:query).with(:foo, [:arguments])
|
79
|
+
instance.method_missing(:foo, [:arguments])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context '#reload_miners!' do
|
84
|
+
before do
|
85
|
+
allow(File).to receive(:exist?).with('config/miners.yml').and_return(true)
|
86
|
+
allow_any_instance_of(CgminerApiClient::MinerPool).to receive(:load_miners!).and_return(true)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should call load_miners!' do
|
90
|
+
expect(instance).to receive(:load_miners!)
|
91
|
+
instance.reload_miners!
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'private methods' do
|
96
|
+
context '#load_miners!' do
|
97
|
+
context 'without configuration file' do
|
98
|
+
before do
|
99
|
+
expect(File).to receive(:exist?).with('config/miners.yml').and_return(false)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'should raise an error' do
|
103
|
+
expect{
|
104
|
+
instance
|
105
|
+
}.to raise_error(RuntimeError)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'with configuration file' do
|
110
|
+
before do
|
111
|
+
allow(File).to receive(:exist?).with('config/miners.yml').and_return(true)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'should parse the configuration file' do
|
115
|
+
expect(YAML).to receive(:load_file).with('config/miners.yml').and_return([mock_miner_from_yaml]).at_least(:once)
|
116
|
+
instance.send(:load_miners!)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'should create new instances of CgminerApiClient::Miner' do
|
120
|
+
allow(YAML).to receive(:load_file).with('config/miners.yml').and_return([mock_miner_from_yaml])
|
121
|
+
expect(CgminerApiClient::Miner).to receive(:new).with(mock_miner_from_yaml[:host], mock_miner_from_yaml[:port], mock_miner_from_yaml[:timeout])
|
122
|
+
instance.send(:load_miners!)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should assign the remote instances to @miners' do
|
126
|
+
allow(YAML).to receive(:load_file).with('config/miners.yml').and_return([mock_miner_from_yaml])
|
127
|
+
allow(CgminerApiClient::Miner).to receive(:new).with(mock_miner_from_yaml[:host], mock_miner_from_yaml[:port], mock_miner_from_yaml[:timeout]).and_return(mock_miner)
|
128
|
+
instance.send(:load_miners!)
|
129
|
+
expect(instance.miners).to eq [mock_miner]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CgminerApiClient::Miner do
|
4
|
+
let(:host) { '127.0.0.1' }
|
5
|
+
let(:port) { 4028 }
|
6
|
+
let(:timeout) { 1 }
|
7
|
+
let(:instance) { CgminerApiClient::Miner.new(host, port, timeout) }
|
8
|
+
|
9
|
+
context 'attributes' do
|
10
|
+
context '@host' do
|
11
|
+
it 'should allow setting and getting' do
|
12
|
+
instance.host = :foo
|
13
|
+
expect(instance.host).to eq :foo
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context '@port' do
|
18
|
+
it 'should allow setting and getting' do
|
19
|
+
instance.port = :foo
|
20
|
+
expect(instance.port).to eq :foo
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context '@timeout' do
|
25
|
+
it 'should allow setting and getting' do
|
26
|
+
instance.timeout = :foo
|
27
|
+
expect(instance.timeout).to eq :foo
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '#initialize' do
|
33
|
+
it 'should not raise an argument error with 0 arguments' do
|
34
|
+
expect {
|
35
|
+
CgminerApiClient::Miner.new
|
36
|
+
}.to_not raise_error()
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should not raise an argument error with 1 arguments' do
|
40
|
+
expect {
|
41
|
+
CgminerApiClient::Miner.new(host)
|
42
|
+
}.to_not raise_error()
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should not raise an argument error with 2 arguments' do
|
46
|
+
expect {
|
47
|
+
CgminerApiClient::Miner.new(host, port)
|
48
|
+
}.to_not raise_error()
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not raise an argument error with 3 arguments' do
|
52
|
+
expect {
|
53
|
+
CgminerApiClient::Miner.new(host, port, timeout)
|
54
|
+
}.to_not raise_error()
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should use defaults' do
|
58
|
+
miner = CgminerApiClient::Miner.new
|
59
|
+
expect(miner.host).to eq CgminerApiClient.default_host
|
60
|
+
expect(miner.port).to eq CgminerApiClient.default_port
|
61
|
+
expect(miner.timeout).to eq CgminerApiClient.default_timeout
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should set @host' do
|
65
|
+
expect(instance.host).to eq host
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should set @port' do
|
69
|
+
expect(instance.port).to eq port
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should set @timeout' do
|
73
|
+
expect(instance.timeout).to eq timeout
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context '#available?' do
|
78
|
+
let(:mock_socket) { instance_double('Socket') }
|
79
|
+
|
80
|
+
context 'open_socket raises an error' do
|
81
|
+
before do
|
82
|
+
expect(instance).to receive(:open_socket).and_raise(SocketError)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should return false' do
|
86
|
+
expect(instance.available?).to eq false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'open_socket does not raise an error' do
|
91
|
+
before do
|
92
|
+
expect(instance).to receive(:open_socket).and_return(mock_socket)
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'socket #close raises an error' do
|
96
|
+
before do
|
97
|
+
expect(mock_socket).to receive(:close).and_raise(SocketError)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'should return false' do
|
101
|
+
expect(instance.available?).to eq false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'socket #close does not raise an error' do
|
106
|
+
before do
|
107
|
+
expect(mock_socket).to receive(:close).and_return(:foo)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should return true' do
|
111
|
+
expect(instance.available?).to eq true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should set an instance variable' do
|
117
|
+
expect(instance).to receive(:open_socket).and_return(mock_socket)
|
118
|
+
expect(mock_socket).to receive(:close).and_return(true)
|
119
|
+
instance.available?
|
120
|
+
expect(instance.instance_variable_get(:@available)).to eq true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context '#query' do
|
125
|
+
context 'unavailable' do
|
126
|
+
before do
|
127
|
+
expect(instance).to receive(:available?).and_return(false)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should not perform a command request' do
|
131
|
+
expect(instance).to receive(:perform_request).never
|
132
|
+
instance.query(:foo)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'should return nil' do
|
136
|
+
expect(instance.query(:foo)).to eq nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'available' do
|
141
|
+
before do
|
142
|
+
expect(instance).to receive(:available?).and_return(true)
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'no parameters' do
|
146
|
+
it 'should perform a command request' do
|
147
|
+
expect(instance).to receive(:perform_request).with({:command => :foo}).and_return({'foo' => []})
|
148
|
+
instance.query(:foo)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context 'parameters' do
|
153
|
+
it 'should perform a command request with parameters' do
|
154
|
+
expect(instance).to receive(:perform_request).with({:command => :foo, :parameter => 'bar,123,\\456'}).and_return({'foo' => []})
|
155
|
+
instance.query(:foo, :bar, :'123', :'\456')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should return sanitized data' do
|
160
|
+
mock_data = double('data')
|
161
|
+
expect(instance).to receive(:perform_request).and_return(mock_data)
|
162
|
+
expect(instance).to receive(:sanitized).with(mock_data).and_return({:foo => []})
|
163
|
+
expect(instance.query(:foo)).to eq []
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should return sanitized data for multiple commands' do
|
167
|
+
mock_data = double('data')
|
168
|
+
expect(instance).to receive(:perform_request).and_return(mock_data)
|
169
|
+
expect(instance).to receive(:sanitized).with(mock_data).and_return({:foo => [], :bar => []})
|
170
|
+
expect(instance.query('foo+bar')).to eq ({:foo => [], :bar => []})
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context '#method_missing' do
|
176
|
+
before do
|
177
|
+
allow(instance).to receive(:query).and_return(true)
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'should query the miner with the method name' do
|
181
|
+
expect(instance).to receive(:query).with(:foo).and_return(true)
|
182
|
+
instance.method_missing(:foo)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should pass arguments' do
|
186
|
+
expect(instance).to receive(:query).with(:foo, [:arguments])
|
187
|
+
instance.method_missing(:foo, [:arguments])
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'private methods' do
|
192
|
+
context '#open_socket' do
|
193
|
+
pending
|
194
|
+
end
|
195
|
+
|
196
|
+
context '#perform_request' do
|
197
|
+
context 'Socket cannot be opened' do
|
198
|
+
before do
|
199
|
+
expect(instance).to receive(:open_socket).and_raise(SocketError)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should raise an exception' do
|
203
|
+
expect {
|
204
|
+
instance.send(:perform_request, {})
|
205
|
+
}.to raise_error(RuntimeError, 'Connection to 127.0.0.1:4028 failed')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context 'Socket can be opened' do
|
210
|
+
let(:mock_socket) { instance_double('Socket', {
|
211
|
+
:write => true,
|
212
|
+
:read => "{'json':true}",
|
213
|
+
:close => true
|
214
|
+
}) }
|
215
|
+
|
216
|
+
before do
|
217
|
+
expect(instance).to receive(:open_socket).and_return(mock_socket)
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'single command' do
|
221
|
+
it 'should parse the response as JSON and check the status' do
|
222
|
+
expect(JSON).to receive(:parse).with(mock_socket.read)
|
223
|
+
expect(instance).to receive(:check_status).and_return(true)
|
224
|
+
instance.send(:perform_request, {})
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'multiple commands' do
|
229
|
+
it 'should parse the response as JSON and check the status of each response element' do
|
230
|
+
expect(JSON).to receive(:parse).with(mock_socket.read).and_return({:foo => [{'STATUS' => 'ALL_GOOD'}], :bar => [{'STATUS' => 'NOT_SO_GOOD'}]})
|
231
|
+
expect(instance).to receive(:check_status).with({"STATUS" => 'ALL_GOOD'})
|
232
|
+
expect(instance).to receive(:check_status).with({"STATUS" => 'NOT_SO_GOOD'})
|
233
|
+
instance.send(:perform_request, {command: 'foo+bar'})
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context '#check_status' do
|
240
|
+
let(:mock_response) { {} }
|
241
|
+
|
242
|
+
context 'with successful status' do
|
243
|
+
before do
|
244
|
+
mock_response['STATUS'] = [{'STATUS' => 'S'}]
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'should not log a message or raise an error' do
|
248
|
+
expect(instance).to receive(:puts).never
|
249
|
+
expect(instance).to receive(:raise).never
|
250
|
+
instance.send(:check_status, mock_response)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'with info status' do
|
255
|
+
before do
|
256
|
+
mock_response['STATUS'] = [{'STATUS' => 'I'}]
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'should log a message' do
|
260
|
+
expect(instance).to receive(:puts)
|
261
|
+
instance.send(:check_status, mock_response)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
context 'with warning status' do
|
266
|
+
before do
|
267
|
+
mock_response['STATUS'] = [{'STATUS' => 'W'}]
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'should log a message' do
|
271
|
+
expect(instance).to receive(:puts)
|
272
|
+
instance.send(:check_status, mock_response)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
context 'with error' do
|
277
|
+
before do
|
278
|
+
mock_response['STATUS'] = [{'STATUS' => 'E'}]
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'should raise an exception' do
|
282
|
+
expect(instance).to receive(:raise)
|
283
|
+
instance.send(:check_status, mock_response)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
context '#sanitized' do
|
289
|
+
let(:mock_data) { {'Ugly Key' => :foo} }
|
290
|
+
|
291
|
+
it 'should produce sensible output' do
|
292
|
+
expect(instance.send(:sanitized, mock_data)).to eq ({:ugly_key => :foo})
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CgminerApiClient do
|
4
|
+
subject { CgminerApiClient }
|
5
|
+
|
6
|
+
before do
|
7
|
+
subject.default_timeout = 5
|
8
|
+
subject.default_port = 4028
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should have a version constant with major.minor.patch' do
|
12
|
+
expect(subject::VERSION).to_not be_empty
|
13
|
+
expect(subject::VERSION.split(/\./).length).to eq(3)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'module attributes' do
|
17
|
+
context 'default_timeout' do
|
18
|
+
it 'should allow setting and getting' do
|
19
|
+
subject.default_timeout = :foo
|
20
|
+
expect(subject.default_timeout).to eq :foo
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'default_port' do
|
25
|
+
it 'should allow setting and getting' do
|
26
|
+
subject.default_port = :foo
|
27
|
+
expect(subject.default_port).to eq :foo
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context '.config' do
|
33
|
+
it 'should yield a block if given' do
|
34
|
+
expect {
|
35
|
+
subject.config do |config|
|
36
|
+
config.default_timeout = :foo
|
37
|
+
end
|
38
|
+
}.to change { subject.default_timeout }.from(subject.default_timeout).to(:foo)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|