mock_dns_server 0.3.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +24 -0
- data/README.md +127 -0
- data/RELEASE_NOTES.md +3 -0
- data/Rakefile +19 -0
- data/bin/show_dig_request +41 -0
- data/lib/mock_dns_server.rb +12 -0
- data/lib/mock_dns_server/action_factory.rb +84 -0
- data/lib/mock_dns_server/conditional_action.rb +42 -0
- data/lib/mock_dns_server/conditional_action_factory.rb +53 -0
- data/lib/mock_dns_server/conditional_actions.rb +73 -0
- data/lib/mock_dns_server/dnsruby_monkey_patch.rb +19 -0
- data/lib/mock_dns_server/history.rb +84 -0
- data/lib/mock_dns_server/history_inspections.rb +58 -0
- data/lib/mock_dns_server/ip_address_dispenser.rb +34 -0
- data/lib/mock_dns_server/message_builder.rb +199 -0
- data/lib/mock_dns_server/message_helper.rb +86 -0
- data/lib/mock_dns_server/message_transformer.rb +74 -0
- data/lib/mock_dns_server/predicate_factory.rb +108 -0
- data/lib/mock_dns_server/serial_history.rb +385 -0
- data/lib/mock_dns_server/serial_number.rb +129 -0
- data/lib/mock_dns_server/serial_transaction.rb +46 -0
- data/lib/mock_dns_server/server.rb +422 -0
- data/lib/mock_dns_server/server_context.rb +57 -0
- data/lib/mock_dns_server/server_thread.rb +13 -0
- data/lib/mock_dns_server/version.rb +3 -0
- data/mock_dns_server.gemspec +32 -0
- data/spec/mock_dns_server/conditions_factory_spec.rb +58 -0
- data/spec/mock_dns_server/history_inspections_spec.rb +84 -0
- data/spec/mock_dns_server/history_spec.rb +65 -0
- data/spec/mock_dns_server/ip_address_dispenser_spec.rb +30 -0
- data/spec/mock_dns_server/message_builder_spec.rb +18 -0
- data/spec/mock_dns_server/predicate_factory_spec.rb +147 -0
- data/spec/mock_dns_server/serial_history_spec.rb +385 -0
- data/spec/mock_dns_server/serial_number_spec.rb +119 -0
- data/spec/mock_dns_server/serial_transaction_spec.rb +37 -0
- data/spec/mock_dns_server/server_context_spec.rb +20 -0
- data/spec/mock_dns_server/server_spec.rb +411 -0
- data/spec/mock_dns_server/socket_research_spec.rb +59 -0
- data/spec/spec_helper.rb +44 -0
- data/todo.txt +0 -0
- metadata +212 -0
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mock_dns_server/serial_transaction'
|
3
|
+
require 'mock_dns_server/message_builder'
|
4
|
+
|
5
|
+
module MockDnsServer
|
6
|
+
|
7
|
+
describe SerialTransaction do
|
8
|
+
|
9
|
+
let(:rrs) do [
|
10
|
+
MessageBuilder.rr('A', 'abc.com', '1.2.3.4'),
|
11
|
+
MessageBuilder.rr('A', 'abc.com', '5.6.7.8')
|
12
|
+
] end
|
13
|
+
|
14
|
+
context 'SerialTransaction#ixfr_records' do
|
15
|
+
|
16
|
+
it "positions SOA's, additions, and deletions correctly" do
|
17
|
+
txn = SerialTransaction.new('ruby-lang.org', 1002)
|
18
|
+
txn.additions = [rrs.first]
|
19
|
+
txn.deletions = [rrs.last]
|
20
|
+
records = txn.ixfr_records(1001)
|
21
|
+
|
22
|
+
expect(records[0].type.to_s).to eq('SOA')
|
23
|
+
expect(records[0].serial).to eq(1001)
|
24
|
+
|
25
|
+
expect(records[1].type.to_s).to eq('A')
|
26
|
+
expect(records[1].rdata.to_s).to eq('5.6.7.8')
|
27
|
+
|
28
|
+
expect(records[2].type.to_s).to eq('SOA')
|
29
|
+
expect(records[2].serial).to eq(1002)
|
30
|
+
|
31
|
+
expect(records[3].type.to_s).to eq('A')
|
32
|
+
expect(records[3].rdata.to_s).to eq('1.2.3.4')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
require 'mock_dns_server/server_context'
|
4
|
+
|
5
|
+
module MockDnsServer
|
6
|
+
|
7
|
+
describe ServerContext do
|
8
|
+
|
9
|
+
subject { ServerContext.new(nil) }
|
10
|
+
|
11
|
+
it 'should wrap a block in a mutex without raising an exception' do
|
12
|
+
n = 0
|
13
|
+
block = ->() do
|
14
|
+
subject.with_mutex { n = 1 }
|
15
|
+
end
|
16
|
+
expect(block).to_not raise_exception
|
17
|
+
expect(n).to eq(1) # prove the block was executed
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,411 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
require 'dnsruby'
|
4
|
+
require 'awesome_print'
|
5
|
+
|
6
|
+
require 'mock_dns_server/action_factory'
|
7
|
+
require 'mock_dns_server/conditional_action_factory'
|
8
|
+
require 'mock_dns_server/message_builder'
|
9
|
+
require 'mock_dns_server/message_helper'
|
10
|
+
require 'mock_dns_server/message_transformer'
|
11
|
+
require 'mock_dns_server/predicate_factory'
|
12
|
+
require 'mock_dns_server/serial_history'
|
13
|
+
require 'mock_dns_server/server'
|
14
|
+
|
15
|
+
module MockDnsServer
|
16
|
+
|
17
|
+
MB = MessageBuilder
|
18
|
+
|
19
|
+
describe Server do
|
20
|
+
|
21
|
+
# This can be enabled temporarily if there are problems with the port being in use.
|
22
|
+
# before(:each) do
|
23
|
+
# Server.kill_all_servers
|
24
|
+
# end
|
25
|
+
|
26
|
+
let(:port) { 9999 }
|
27
|
+
let(:host) { '127.0.0.1' }
|
28
|
+
let(:options) { { host: host, port: port } }
|
29
|
+
# let(:options) { { host: host, port: port, verbose: true } }
|
30
|
+
let(:verbose_options) { options.merge({ verbose: true }) }
|
31
|
+
let(:sample_message) { 'Hello from RSpec' }
|
32
|
+
let(:pf) { PredicateFactory.new }
|
33
|
+
let(:af) { ActionFactory.new }
|
34
|
+
let(:caf) { ConditionalActionFactory.new }
|
35
|
+
let(:mb) { MessageBuilder.new }
|
36
|
+
|
37
|
+
#it 'runs an echo server' do
|
38
|
+
# opts = verbose_options.merge(host: '10.0.1.8')
|
39
|
+
# ap opts
|
40
|
+
# Server.with_new_server(opts) do |server|
|
41
|
+
# server.add_conditional_action(ConditionalActionFactory.new.echo)
|
42
|
+
# server.start
|
43
|
+
# sleep 1000000
|
44
|
+
# end
|
45
|
+
#end
|
46
|
+
|
47
|
+
|
48
|
+
it 'can be created and destroyed twice successfully' do
|
49
|
+
2.times do
|
50
|
+
server = Server.new(options).start
|
51
|
+
server.wait_until_ready
|
52
|
+
server.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'gets created and destroyed' do
|
57
|
+
|
58
|
+
server = Server.new(options)
|
59
|
+
|
60
|
+
expect(server.ready?).to be false
|
61
|
+
expect(server.closed?).to be false
|
62
|
+
|
63
|
+
server.start.wait_until_ready
|
64
|
+
expect(server.ready?).to be true
|
65
|
+
expect(server.closed?).to be false
|
66
|
+
server.close
|
67
|
+
expect(server.closed?).to be true
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
it 'can be created and destroyed twice successfully' do
|
72
|
+
2.times do
|
73
|
+
server = Server.new(options).start
|
74
|
+
server.wait_until_ready
|
75
|
+
server.close
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'gets and replies to a simple message with TCP' do
|
80
|
+
Server.with_new_server(options) do |server|
|
81
|
+
server.add_conditional_action(ConditionalActionFactory.new.echo)
|
82
|
+
server.start.wait_until_ready
|
83
|
+
client_socket = TCPSocket.open(host, port)
|
84
|
+
client_socket.write(MessageHelper.tcp_message_package_for_write(sample_message))
|
85
|
+
message = MessageHelper.read_tcp_message(client_socket)
|
86
|
+
expect(message).to eq(sample_message)
|
87
|
+
# TODO: Close these sockets?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'instructs the server correctly to be an echo server' do
|
92
|
+
Server.with_new_server(options) do |server|
|
93
|
+
server.add_conditional_action(ConditionalActionFactory.new.echo)
|
94
|
+
server.start.wait_until_ready
|
95
|
+
socket = UDPSocket.new
|
96
|
+
socket.send(sample_message, 0, host, port)
|
97
|
+
request_received, _ = socket.recvfrom(10_000)
|
98
|
+
expect(request_received).to eq(sample_message)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
it 'gets a simple message with UDP' do
|
105
|
+
|
106
|
+
Server.with_new_server(options) do |server|
|
107
|
+
|
108
|
+
server.start do |request, sender, _|
|
109
|
+
server.send_udp_response(sender, request)
|
110
|
+
end
|
111
|
+
|
112
|
+
socket = UDPSocket.new
|
113
|
+
server.wait_until_ready
|
114
|
+
socket.send(sample_message, 0, host, port)
|
115
|
+
request_received, _ = socket.recvfrom(10_000)
|
116
|
+
expect(request_received).to eq(sample_message)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
it 'calculates conditional action counts correctly' do
|
123
|
+
Server.with_new_server(options) do |server|
|
124
|
+
expect(server.conditional_action_count).to eq(0)
|
125
|
+
server.add_conditional_action(ConditionalActionFactory.new.echo)
|
126
|
+
expect(server.conditional_action_count).to eq(1)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
it 'handles maximum counts correctly' do
|
132
|
+
|
133
|
+
# TODO: Research whether the placing of the sleep calls in this test
|
134
|
+
# (and possibly elsewhere) indicate design or documentation error.
|
135
|
+
#
|
136
|
+
ca1 = ConditionalAction.new(pf.always_true, af.constant('1'), 1)
|
137
|
+
ca2 = ConditionalAction.new(pf.always_true, af.constant('2'), 1)
|
138
|
+
|
139
|
+
Server.with_new_server(options) do |server|
|
140
|
+
server.add_conditional_actions([ca1, ca2])
|
141
|
+
expect(server.conditional_action_count).to eq(2)
|
142
|
+
server.start.wait_until_ready
|
143
|
+
|
144
|
+
socket = UDPSocket.new
|
145
|
+
socket.send(sample_message, 0, host, port)
|
146
|
+
request_received, _ = socket.recvfrom(10_000)
|
147
|
+
expect(request_received).to eq('2')
|
148
|
+
sleep 0.01
|
149
|
+
expect(server.conditional_action_count).to eq(1)
|
150
|
+
|
151
|
+
socket.send(sample_message, 0, host, port)
|
152
|
+
request_received, _ = socket.recvfrom(10_000)
|
153
|
+
expect(request_received).to eq('1')
|
154
|
+
sleep 0.01
|
155
|
+
expect(server.conditional_action_count).to eq(0)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
|
160
|
+
it 'correctly returns an A response' do
|
161
|
+
|
162
|
+
domain_name = 'foo.example.com'
|
163
|
+
response_data = "#{domain_name} 86400 A 10.1.2.3"
|
164
|
+
|
165
|
+
Server.with_new_server(options) do |server|
|
166
|
+
server.add_conditional_action(caf.specified_a_response(domain_name, response_data))
|
167
|
+
server.start.wait_until_ready
|
168
|
+
|
169
|
+
request = Dnsruby::Message.new(domain_name).encode
|
170
|
+
socket = UDPSocket.new
|
171
|
+
socket.send(request, 0, host, port)
|
172
|
+
encoded_message, _ = socket.recvfrom(10_000)
|
173
|
+
response = Dnsruby::Message.decode(encoded_message)
|
174
|
+
|
175
|
+
expect(response.answer.size).to eq(1)
|
176
|
+
answer = response.answer.first
|
177
|
+
expect(answer).to be_a(Dnsruby::RR::IN::A)
|
178
|
+
expect(answer.ttl).to eq(86400)
|
179
|
+
expect(answer.name.to_s).to eq(domain_name)
|
180
|
+
expect(answer.rdata.to_s).to eq('10.1.2.3')
|
181
|
+
expect(answer.rr_type).to eq('A')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
it 'should not provide access to its internals' do
|
187
|
+
internal_methods = [:history, :conditional_actions]
|
188
|
+
server = Server.new(options)
|
189
|
+
errors = internal_methods.select { |pm| server.respond_to?(pm) }
|
190
|
+
server.close
|
191
|
+
expect(errors).to eq([])
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
it 'should get a DNS message correctly with UDP' do
|
196
|
+
Server.with_new_server(options) do |server|
|
197
|
+
|
198
|
+
server.add_conditional_action(caf.echo)
|
199
|
+
server.start.wait_until_ready
|
200
|
+
|
201
|
+
socket = UDPSocket.new
|
202
|
+
wire_data = Dnsruby::Message.new('a.com').encode
|
203
|
+
socket.send(wire_data, 0, host, port)
|
204
|
+
response_wire_data, _ = socket.recvfrom(2000)
|
205
|
+
expect(server.is_dns_packet?(response_wire_data, :udp)).to be true
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
|
210
|
+
it 'should instantiate without specifying an options hash' do
|
211
|
+
old_verbose, $VERBOSE = $VERBOSE, nil # disable 'constant already initialized' warning
|
212
|
+
default_port_sav = Server::DEFAULT_PORT
|
213
|
+
Server::DEFAULT_PORT = 9999
|
214
|
+
expect(->() { Server.new.close } ).to_not raise_error
|
215
|
+
Server::DEFAULT_PORT = default_port_sav
|
216
|
+
$VERBOSE = old_verbose
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def test_dig_request(protocol)
|
221
|
+
Server.with_new_server(options) do |server|
|
222
|
+
response_data = "foo.example.com 86400 A 10.1.2.3"
|
223
|
+
server.add_conditional_action(caf.specified_a_response('foo.example.com', response_data))
|
224
|
+
server.start.wait_until_ready
|
225
|
+
dig_command = "dig @#{options[:host]} -p #{options[:port]} foo.example.com #{protocol == :tcp ? ' +tcp' : ''}"
|
226
|
+
dig_output = `#{dig_command}`
|
227
|
+
expect(dig_output).to include('status: NOERROR')
|
228
|
+
# expect(/foo.example.com.\s+86400\s+IN\s+A\s+10.1.2.3/ === dig_output).to be_true
|
229
|
+
expect(dig_output).to match(/foo.example.com.\s+86400\s+IN\s+A\s+10.1.2.3/)
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should respond correctly to a TCP dig request' do
|
235
|
+
test_dig_request(:tcp)
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
it 'should respond correctly to a UDP dig request' do
|
240
|
+
test_dig_request(:udp)
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
it 'should get a DNS message correctly with TCP' do
|
245
|
+
Server.with_new_server(options) do |server|
|
246
|
+
|
247
|
+
server.add_conditional_action(caf.echo)
|
248
|
+
server.start.wait_until_ready
|
249
|
+
|
250
|
+
client = TCPSocket.new('localhost', options[:port])
|
251
|
+
|
252
|
+
message = Dnsruby::Message.new('a.com')
|
253
|
+
wire_data = MessageHelper.tcp_message_package_for_write(message)
|
254
|
+
client.write(wire_data)
|
255
|
+
response_wire_data, _ = client.read(wire_data.size)
|
256
|
+
expect(server.is_dns_packet?(response_wire_data, :udp)).to be true
|
257
|
+
|
258
|
+
client.close
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
it 'should return an AXFR response correctly' do
|
264
|
+
|
265
|
+
zone = 'ruby-lang.org'
|
266
|
+
dns_records = [
|
267
|
+
MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'),
|
268
|
+
MB.rr('A', 'x.ruby-lang.org', '1.1.1.3'),
|
269
|
+
]
|
270
|
+
|
271
|
+
Server.with_new_server(options) do |server|
|
272
|
+
|
273
|
+
serial_history = SerialHistory.new(zone, 3001, dns_records)
|
274
|
+
server.add_conditional_action(caf.zone_load(serial_history))
|
275
|
+
server.start.wait_until_ready
|
276
|
+
|
277
|
+
client = TCPSocket.new('localhost', options[:port])
|
278
|
+
message = MB.axfr_request(zone)
|
279
|
+
wire_data = MessageHelper.tcp_message_package_for_write(message)
|
280
|
+
client.write(wire_data)
|
281
|
+
response, _ = MessageHelper.read_tcp_message(client)
|
282
|
+
|
283
|
+
answers = response.answer.to_a
|
284
|
+
check_soa = check_soa_fn(answers, zone)
|
285
|
+
check_a = check_a_fn(answers, 'x.ruby-lang.org')
|
286
|
+
|
287
|
+
check_soa.(0, 3001)
|
288
|
+
check_a.(1, '1.1.1.2')
|
289
|
+
check_a.(2, '1.1.1.3')
|
290
|
+
check_soa.(3, 3001)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
it 'should return an IXFR response correctly' do
|
296
|
+
|
297
|
+
zone = 'ruby-lang.org'
|
298
|
+
|
299
|
+
serial_history = ->() do
|
300
|
+
history = SerialHistory.new(zone, 3001)
|
301
|
+
history.set_serial_additions(3002, MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'))
|
302
|
+
history.set_serial_additions(3003, MB.rr('A', 'x.ruby-lang.org', '1.1.1.3'))
|
303
|
+
history.set_serial_additions(3004, MB.rr('A', 'x.ruby-lang.org', '1.1.1.4'))
|
304
|
+
history.set_serial_deletions(3004, MB.rr('A', 'x.ruby-lang.org', '1.1.1.2'))
|
305
|
+
history.set_serial_additions(3005, MB.rr('A', 'x.ruby-lang.org', '1.1.1.5'))
|
306
|
+
history
|
307
|
+
end
|
308
|
+
|
309
|
+
Server.with_new_server(options) do |server|
|
310
|
+
|
311
|
+
server.add_conditional_action(caf.zone_load(serial_history.()))
|
312
|
+
server.start.wait_until_ready
|
313
|
+
|
314
|
+
# Can put a pry here and then:
|
315
|
+
# .dig -p 9999 @127.0.0.1 ruby-lang.org ixfr=3003
|
316
|
+
|
317
|
+
client = TCPSocket.new('localhost', options[:port])
|
318
|
+
query = MB.ixfr_request(zone, 3003)
|
319
|
+
wire_data = MessageHelper.tcp_message_package_for_write(query)
|
320
|
+
client.write(wire_data)
|
321
|
+
response, _ = MessageHelper.read_tcp_message(client)
|
322
|
+
#puts `dig -p 9999 @127.0.0.1 ruby-lang.org ixfr=3001 2>&1`
|
323
|
+
|
324
|
+
expect(response).to be_a(Dnsruby::Message)
|
325
|
+
|
326
|
+
answers = response.answer.to_a
|
327
|
+
|
328
|
+
check_soa = check_soa_fn(answers, zone)
|
329
|
+
check_a = check_a_fn(answers, 'x.ruby-lang.org')
|
330
|
+
|
331
|
+
check_soa.(0, 3005)
|
332
|
+
|
333
|
+
check_soa.(1, 3003)
|
334
|
+
check_a.(2, '1.1.1.2')
|
335
|
+
|
336
|
+
check_soa.(3, 3004)
|
337
|
+
check_a.(4, '1.1.1.4')
|
338
|
+
|
339
|
+
check_soa.(5, 3004)
|
340
|
+
|
341
|
+
check_soa.(6, 3005)
|
342
|
+
check_a.(7, '1.1.1.5')
|
343
|
+
|
344
|
+
check_soa.(8, 3005)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
it 'servers work when there are multiple servers running' do
|
350
|
+
begin
|
351
|
+
ports = [9998, 9999]
|
352
|
+
servers = ports.map { |port| Server.new(options.merge(port: port)) }
|
353
|
+
servers.each do |server|
|
354
|
+
serial = server.port # for convenience, use server's port as the serial
|
355
|
+
server.add_conditional_action(caf.soa_response(serial, 'com'))
|
356
|
+
server.start.wait_until_ready
|
357
|
+
end
|
358
|
+
|
359
|
+
servers.each do |server|
|
360
|
+
request = MB.soa_request('com')
|
361
|
+
response = MessageHelper.send_udp_and_get_response(request, host, server.port)
|
362
|
+
expect(MessageTransformer.new(response).serial).to eq(server.port)
|
363
|
+
end
|
364
|
+
|
365
|
+
ensure
|
366
|
+
if servers
|
367
|
+
servers.each { |server| server.close }
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
it 'can close multiple times without error' do
|
374
|
+
expect(->() { Server.with_new_server(options) do |server|
|
375
|
+
server.close
|
376
|
+
server.close
|
377
|
+
end }).not_to raise_error
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
it 'will not allow starting a server (and spawning a new thread) more than once' do
|
382
|
+
test_lambda = ->() do
|
383
|
+
Server.with_new_server(options) do |server|
|
384
|
+
server.start.wait_until_ready
|
385
|
+
server.start
|
386
|
+
end
|
387
|
+
end
|
388
|
+
expect(test_lambda).to raise_error
|
389
|
+
end
|
390
|
+
|
391
|
+
|
392
|
+
it 'kill_all_servers works' do
|
393
|
+
|
394
|
+
Server.kill_all_servers # make sure we're starting with zero servers
|
395
|
+
ports = [9998, 9999]
|
396
|
+
servers = ports.map { |port| Server.new(options.merge({ port: port }))}
|
397
|
+
|
398
|
+
servers.each do |server|
|
399
|
+
# for convenience, use server's port as the serial
|
400
|
+
server.add_conditional_action(caf.soa_response(1001, 'com'))
|
401
|
+
server.start.wait_until_ready
|
402
|
+
end
|
403
|
+
|
404
|
+
expect(servers.size).to eq(2)
|
405
|
+
expect(ServerThread.all.size).to eq(2)
|
406
|
+
Server.kill_all_servers
|
407
|
+
sleep 1
|
408
|
+
expect(ServerThread.all.size).to eq(0)
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|