kamerling 0.0.2 → 0.0.3
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 +4 -4
- data/.rubocop.yml +4 -13
- data/Gemfile +0 -1
- data/Gemfile.lock +64 -64
- data/README.md +18 -1
- data/Rakefile +5 -3
- data/bin/kamerling +1 -1
- data/config/reek.yml +20 -3
- data/kamerling.gemspec +16 -13
- data/lib/kamerling.rb +2 -28
- data/lib/kamerling/addr.rb +17 -3
- data/lib/kamerling/client.rb +10 -11
- data/lib/kamerling/core_extensions/main.rb +14 -14
- data/lib/kamerling/dispatch.rb +13 -0
- data/lib/kamerling/handler.rb +16 -25
- data/lib/kamerling/http_api.rb +50 -34
- data/lib/kamerling/logging.rb +36 -16
- data/lib/kamerling/mapper.rb +33 -0
- data/lib/kamerling/message.rb +50 -37
- data/lib/kamerling/migrations/2_results_received_at.rb +7 -0
- data/lib/kamerling/migrations/3_dispatches.rb +17 -0
- data/lib/kamerling/migrations/4_registrations_registered_at.rb +7 -0
- data/lib/kamerling/migrations/5_clients_type.rb +7 -0
- data/lib/kamerling/net_dispatcher.rb +12 -7
- data/lib/kamerling/project.rb +7 -3
- data/lib/kamerling/receiver.rb +38 -10
- data/lib/kamerling/registrar.rb +45 -8
- data/lib/kamerling/registration.rb +9 -10
- data/lib/kamerling/repo.rb +33 -26
- data/lib/kamerling/repos.rb +52 -45
- data/lib/kamerling/result.rb +10 -11
- data/lib/kamerling/server/http.rb +28 -21
- data/lib/kamerling/server/sock.rb +32 -24
- data/lib/kamerling/server/tcp.rb +23 -15
- data/lib/kamerling/server/udp.rb +24 -16
- data/lib/kamerling/server_runner.rb +30 -41
- data/lib/kamerling/settings.rb +28 -0
- data/lib/kamerling/task.rb +7 -7
- data/lib/kamerling/task_dispatcher.rb +31 -22
- data/lib/kamerling/uuid.rb +13 -11
- data/lib/kamerling/uuid_entity.rb +23 -9
- data/lib/kamerling/value.rb +13 -0
- data/lib/kamerling/views/clients.slim +3 -1
- data/lib/kamerling/views/project.slim +8 -4
- data/lib/kamerling/views/projects.slim +7 -1
- data/spec/kamerling/addr_spec.rb +32 -22
- data/spec/kamerling/client_spec.rb +9 -5
- data/spec/kamerling/core_extensions/main_spec.rb +18 -13
- data/spec/kamerling/dispatch_spec.rb +16 -0
- data/spec/kamerling/handler_spec.rb +24 -34
- data/spec/kamerling/http_api_spec.rb +94 -73
- data/spec/kamerling/logging_spec.rb +93 -62
- data/spec/kamerling/mapper_spec.rb +151 -0
- data/spec/kamerling/message_spec.rb +73 -49
- data/spec/kamerling/net_dispatcher_spec.rb +22 -16
- data/spec/kamerling/receiver_spec.rb +29 -19
- data/spec/kamerling/registrar_spec.rb +43 -15
- data/spec/kamerling/registration_spec.rb +17 -0
- data/spec/kamerling/repo_spec.rb +63 -47
- data/spec/kamerling/repos_spec.rb +121 -109
- data/spec/kamerling/result_spec.rb +16 -0
- data/spec/kamerling/server/http_spec.rb +19 -14
- data/spec/kamerling/server/tcp_spec.rb +41 -35
- data/spec/kamerling/server/udp_spec.rb +40 -34
- data/spec/kamerling/server_runner_spec.rb +62 -53
- data/spec/kamerling/settings_spec.rb +36 -0
- data/spec/kamerling/task_dispatcher_spec.rb +38 -15
- data/spec/kamerling/task_spec.rb +9 -5
- data/spec/kamerling/uuid_entity_spec.rb +53 -25
- data/spec/kamerling/uuid_spec.rb +19 -16
- data/spec/kamerling/value_spec.rb +21 -0
- data/spec/spec_helper.rb +3 -6
- metadata +54 -8
- data/lib/kamerling/core_extensions.rb +0 -1
@@ -1,9 +1,13 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/kamerling/addr'
|
3
|
+
require_relative '../../lib/kamerling/client'
|
2
4
|
|
3
|
-
module Kamerling
|
4
|
-
describe
|
5
|
-
|
6
|
-
|
5
|
+
module Kamerling
|
6
|
+
describe Client do
|
7
|
+
describe '#busy' do
|
8
|
+
it 'defaults to false' do
|
9
|
+
refute Client.new.busy
|
10
|
+
end
|
7
11
|
end
|
8
12
|
end
|
9
|
-
end
|
13
|
+
end
|
@@ -1,18 +1,23 @@
|
|
1
1
|
require_relative '../../spec_helper'
|
2
|
+
require_relative '../../../lib/kamerling/core_extensions/main'
|
2
3
|
|
3
|
-
module Kamerling
|
4
|
-
describe
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
module Kamerling
|
5
|
+
describe CoreExtensions::Main do
|
6
|
+
describe '#warn_off' do
|
7
|
+
before { @verbose = $VERBOSE }
|
8
|
+
after { $VERBOSE = @verbose }
|
9
|
+
|
10
|
+
it 'when $VERBOSE is on it turns it off inside the block and back on' do
|
11
|
+
$VERBOSE = true
|
12
|
+
CoreExtensions::Main.warn_off { refute $VERBOSE }
|
13
|
+
assert $VERBOSE
|
14
|
+
end
|
10
15
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
+
it 'when $VERBOSE is off it keeps it off' do
|
17
|
+
$VERBOSE = false
|
18
|
+
CoreExtensions::Main.warn_off { refute $VERBOSE }
|
19
|
+
refute $VERBOSE
|
20
|
+
end
|
16
21
|
end
|
17
22
|
end
|
18
|
-
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/kamerling/dispatch'
|
3
|
+
|
4
|
+
module Kamerling
|
5
|
+
describe Dispatch do
|
6
|
+
describe '#dispatched_at' do
|
7
|
+
it 'defaults to the current time' do
|
8
|
+
assert Dispatch.new.dispatched_at.between?(Time.now - 1, Time.now + 1)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'defaults to the time of Dispatch’s creation' do
|
12
|
+
Dispatch.new.dispatched_at.wont_equal Dispatch.new.dispatched_at
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,41 +1,31 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
|
+
require_relative '../../lib/kamerling/addr'
|
3
|
+
require_relative '../../lib/kamerling/handler'
|
4
|
+
require_relative '../../lib/kamerling/message'
|
5
|
+
require_relative '../../lib/kamerling/receiver'
|
6
|
+
require_relative '../../lib/kamerling/registrar'
|
7
|
+
require_relative '../../lib/kamerling/uuid'
|
2
8
|
|
3
|
-
module Kamerling
|
4
|
-
describe
|
5
|
-
|
6
|
-
|
9
|
+
module Kamerling
|
10
|
+
describe Handler do
|
11
|
+
describe '#handle' do
|
12
|
+
fake :receiver, as: :class
|
13
|
+
fake :registrar, as: :class
|
7
14
|
|
8
|
-
|
9
|
-
|
10
|
-
client_uuid = UUID['16B client UUID']
|
11
|
-
project_uuid = UUID['16B project UUID']
|
12
|
-
handler.handle input, addr
|
13
|
-
args = {
|
14
|
-
addr: addr,
|
15
|
-
client_uuid: client_uuid,
|
16
|
-
project_uuid: project_uuid,
|
17
|
-
}
|
18
|
-
registrar.must_have_received :register, [args]
|
19
|
-
end
|
15
|
+
let(:addr) { Addr.new }
|
16
|
+
let(:handler) { Handler.new(receiver: receiver, registrar: registrar) }
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
handler.handle input, addr
|
27
|
-
args = {
|
28
|
-
addr: addr,
|
29
|
-
client_uuid: client_uuid,
|
30
|
-
data: 'data',
|
31
|
-
task_uuid: task_uuid,
|
32
|
-
}
|
33
|
-
receiver.must_have_received :receive, [args]
|
34
|
-
end
|
18
|
+
it 'handles RGST inputs' do
|
19
|
+
message = Message.parse('RGST')
|
20
|
+
handler.handle message, addr
|
21
|
+
registrar.must_have_received :register, [addr: addr, message: message]
|
22
|
+
end
|
35
23
|
|
36
|
-
|
37
|
-
|
38
|
-
|
24
|
+
it 'handles RSLT inputs' do
|
25
|
+
message = Message.parse('RSLT')
|
26
|
+
handler.handle message, addr
|
27
|
+
receiver.must_have_received :receive, [addr: addr, message: message]
|
28
|
+
end
|
39
29
|
end
|
40
30
|
end
|
41
|
-
end
|
31
|
+
end
|
@@ -1,93 +1,114 @@
|
|
1
1
|
require 'nokogiri'
|
2
|
-
|
3
2
|
require_relative '../spec_helper'
|
3
|
+
require_relative '../../lib/kamerling/client'
|
4
|
+
require_relative '../../lib/kamerling/http_api'
|
5
|
+
require_relative '../../lib/kamerling/project'
|
6
|
+
require_relative '../../lib/kamerling/repos'
|
7
|
+
require_relative '../../lib/kamerling/task'
|
8
|
+
require_relative '../../lib/kamerling/task_dispatcher'
|
9
|
+
require_relative '../../lib/kamerling/uuid'
|
4
10
|
|
5
|
-
module Kamerling
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
+
module Kamerling
|
12
|
+
describe HTTPAPI do
|
13
|
+
let(:app) { HTTPAPI.set(repos: repos, task_dispatcher: task_dispatcher) }
|
14
|
+
let(:doc) { Nokogiri::HTML(last_response.body) }
|
15
|
+
let(:ecc) { Project.new }
|
16
|
+
let(:gimps) { Project.new }
|
17
|
+
let(:repos) { fake(:repos, as: :class, projects: [gimps, ecc]) }
|
18
|
+
let(:task_dispatcher) { fake(:task_dispatcher) }
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
describe 'GET /' do
|
21
|
+
it 'contains links to clients and projects' do
|
22
|
+
get '/'
|
23
|
+
doc.at('#clients')['href'].must_equal '/clients'
|
24
|
+
doc.at('#projects')['href'].must_equal '/projects'
|
25
|
+
end
|
17
26
|
end
|
18
|
-
end
|
19
27
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
.must_equal
|
28
|
+
describe 'GET /clients' do
|
29
|
+
it 'contains information on clients' do
|
30
|
+
addr = Addr['127.0.0.1', 1981, :TCP]
|
31
|
+
fpga = Client.new(addr: addr, busy: true, type: :FPGA)
|
32
|
+
stub(repos).clients { [fpga] }
|
33
|
+
get '/clients'
|
34
|
+
links = doc.css('#clients a[data-class=client]')
|
35
|
+
links.first['data-addr'].must_equal 'tcp://127.0.0.1:1981'
|
36
|
+
links.first['data-busy'].must_equal 'true'
|
37
|
+
links.first['data-type'].must_equal 'FPGA'
|
38
|
+
links.first['data-uuid'].must_equal fpga.uuid
|
39
|
+
links.first['href'].must_equal "/clients/#{fpga.uuid}"
|
40
|
+
end
|
29
41
|
end
|
30
|
-
end
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
describe 'GET /projects' do
|
44
|
+
it 'contains links to and UUIDs of projects' do
|
45
|
+
get '/projects'
|
46
|
+
links = doc.css('#projects a[data-class=project]')
|
47
|
+
links.size.must_equal 2
|
48
|
+
links.at("[data-uuid='#{gimps.uuid}']")['href']
|
49
|
+
.must_equal "/projects/#{gimps.uuid}"
|
50
|
+
end
|
39
51
|
end
|
40
|
-
end
|
41
52
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
53
|
+
describe 'GET /projects/{uuid}' do
|
54
|
+
let(:cpu) { Client.new(busy: false, type: :CPU) }
|
55
|
+
let(:gpu) { Client.new(busy: true, type: :GPU) }
|
56
|
+
let(:three) { Task.new(done: false) }
|
57
|
+
let(:seven) { Task.new(done: true) }
|
47
58
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
59
|
+
before do
|
60
|
+
stub(repos).project(gimps.uuid) { gimps }
|
61
|
+
stub(repos).clients_for(gimps) { [cpu, gpu] }
|
62
|
+
stub(repos).tasks_for(gimps) { [three, seven] }
|
63
|
+
end
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
65
|
+
it 'contains links to and info on the project’s clients' do
|
66
|
+
get "/projects/#{gimps.uuid}"
|
67
|
+
links = doc.css('#clients a[data-class=client]')
|
68
|
+
links.size.must_equal 2
|
69
|
+
links.at("[data-uuid='#{cpu.uuid}']")['href']
|
70
|
+
.must_equal "/clients/#{cpu.uuid}"
|
71
|
+
links.at("[data-uuid='#{cpu.uuid}']")['data-busy'].must_equal 'false'
|
72
|
+
links.at("[data-uuid='#{gpu.uuid}']")['data-busy'].must_equal 'true'
|
73
|
+
links.at("[data-uuid='#{cpu.uuid}']")['data-type'].must_equal 'CPU'
|
74
|
+
links.at("[data-uuid='#{gpu.uuid}']")['data-type'].must_equal 'GPU'
|
75
|
+
end
|
63
76
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
77
|
+
it 'contains links to and info on the project’s tasks' do
|
78
|
+
get "/projects/#{gimps.uuid}"
|
79
|
+
links = doc.css('#tasks a[data-class=task]')
|
80
|
+
links.size.must_equal 2
|
81
|
+
links.at("[data-uuid='#{three.uuid}']")['href']
|
82
|
+
.must_equal "/tasks/#{three.uuid}"
|
83
|
+
links.at("[data-uuid='#{three.uuid}']")['data-done'].must_equal 'false'
|
84
|
+
links.at("[data-uuid='#{seven.uuid}']")['data-done'].must_equal 'true'
|
85
|
+
end
|
72
86
|
end
|
73
|
-
end
|
74
87
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
88
|
+
describe 'POST /projects' do
|
89
|
+
it 'creates a new project with the given name and UUID' do
|
90
|
+
post '/projects', name: 'ECC', uuid: uuid = UUID.new
|
91
|
+
repos.must_have_received :<<, [Project.new(name: 'ECC', uuid: uuid)]
|
92
|
+
end
|
80
93
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
94
|
+
it 'redirects to /projects' do
|
95
|
+
post '/projects', name: 'ECC', uuid: UUID.new
|
96
|
+
follow_redirect!
|
97
|
+
URI(last_request.url).path.must_equal '/projects'
|
98
|
+
end
|
85
99
|
end
|
86
100
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
101
|
+
describe 'POST /projects/dispatch' do
|
102
|
+
it 'dispatches tasks to all free clients' do
|
103
|
+
post '/projects/dispatch'
|
104
|
+
task_dispatcher.must_have_received :dispatch_all, []
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'redirects to /projects' do
|
108
|
+
post '/projects/dispatch'
|
109
|
+
follow_redirect!
|
110
|
+
URI(last_request.url).path.must_equal '/projects'
|
111
|
+
end
|
91
112
|
end
|
92
113
|
end
|
93
|
-
end
|
114
|
+
end
|
@@ -1,80 +1,111 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'socket'
|
3
|
+
require 'stringio'
|
1
4
|
require_relative '../spec_helper'
|
5
|
+
require_relative '../../lib/kamerling/addr'
|
6
|
+
require_relative '../../lib/kamerling/logging'
|
7
|
+
require_relative '../../lib/kamerling/message'
|
8
|
+
require_relative '../../lib/kamerling/net_dispatcher'
|
9
|
+
require_relative '../../lib/kamerling/server/tcp'
|
10
|
+
require_relative '../../lib/kamerling/server/udp'
|
2
11
|
|
3
|
-
module Kamerling
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
12
|
+
module Kamerling
|
13
|
+
describe Logging do
|
14
|
+
let(:logged) { stream.tap(&:rewind).read }
|
15
|
+
let(:logger) { Logger.new(stream) }
|
16
|
+
let(:stream) { StringIO.new }
|
17
|
+
let(:tcp_server) { Server::TCP.new(addr: Addr['localhost', 1981, :TCP]) }
|
18
|
+
let(:udp_server) { Server::UDP.new(addr: Addr['localhost', 1979, :UDP]) }
|
9
19
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
after do
|
17
|
-
tcp_server.stop
|
18
|
-
udp_server.stop
|
19
|
-
end
|
20
|
-
|
21
|
-
describe '.log_to' do
|
22
|
-
it 'logs TCP server starts' do
|
23
|
-
logged.must_include 'start localhost:1981 (TCP)'
|
20
|
+
before do
|
21
|
+
Logging.log_to logger
|
22
|
+
tcp_server.start
|
23
|
+
udp_server.start
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
after do
|
27
27
|
tcp_server.stop
|
28
|
-
|
28
|
+
udp_server.stop
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
describe '.log_to' do
|
32
|
+
it 'logs TCP server starts' do
|
33
|
+
logged.must_include 'start tcp://localhost:1981'
|
34
34
|
end
|
35
|
-
run_all_threads
|
36
|
-
logged.must_include "connect #{tcp_addr}"
|
37
|
-
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
Addr[*socket.local_address.ip_unpack, :TCP]
|
36
|
+
it 'logs TCP server stops' do
|
37
|
+
tcp_server.stop
|
38
|
+
logged.must_include 'stop tcp://localhost:1981'
|
43
39
|
end
|
44
|
-
run_all_threads
|
45
|
-
logged.must_include "received #{tcp_addr} PING"
|
46
|
-
end
|
47
40
|
|
48
|
-
|
49
|
-
|
50
|
-
|
41
|
+
it 'logs TCP server connects' do
|
42
|
+
tcp_addr = TCPSocket.open(*tcp_server.addr) do |socket|
|
43
|
+
Addr[*socket.local_address.ip_unpack, :TCP]
|
44
|
+
end
|
45
|
+
run_all_threads
|
46
|
+
logged.must_include "connect #{tcp_addr}"
|
47
|
+
end
|
51
48
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
it 'logs TCP server receives' do
|
50
|
+
tcp_addr = TCPSocket.open(*tcp_server.addr) do |socket|
|
51
|
+
socket << 'PING'
|
52
|
+
Addr[*socket.local_address.ip_unpack, :TCP]
|
53
|
+
end
|
54
|
+
run_all_threads
|
55
|
+
logged.must_include "received #{tcp_addr} 50 49 4e 47"
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
it 'logs TCP unknown message types' do
|
59
|
+
tcp_addr = TCPSocket.open(*tcp_server.addr) do |socket|
|
60
|
+
socket << 'foo'
|
61
|
+
Addr[*socket.local_address.ip_unpack, :TCP]
|
62
|
+
end
|
63
|
+
run_all_threads
|
64
|
+
logged.must_include "received #{tcp_addr} unknown message type"
|
65
|
+
end
|
64
66
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
67
|
+
it 'logs UDP server starts' do
|
68
|
+
logged.must_include 'start udp://localhost:1979'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'logs UDP server stops' do
|
72
|
+
udp_server.stop
|
73
|
+
logged.must_include 'stop udp://localhost:1979'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'logs UDP server connects' do
|
77
|
+
udp_client = UDPSocket.new
|
78
|
+
udp_client.send 'PING', 0, *udp_server.addr
|
79
|
+
udp_addr = Addr['127.0.0.1', udp_client.addr[1], :UDP]
|
80
|
+
run_all_threads
|
81
|
+
logged.must_include "connect #{udp_addr}"
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'logs UDP server receives' do
|
85
|
+
udp_client = UDPSocket.new
|
86
|
+
udp_client.send 'PING', 0, *udp_server.addr
|
87
|
+
udp_addr = Addr['127.0.0.1', udp_client.addr[1], :UDP]
|
88
|
+
run_all_threads
|
89
|
+
logged.must_include "received #{udp_addr} 50 49 4e 47"
|
90
|
+
end
|
72
91
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
92
|
+
it 'logs UDP unknown message types' do
|
93
|
+
udp_client = UDPSocket.new
|
94
|
+
addrs = Array.new(3) do
|
95
|
+
udp_client.send 'foo', 0, *udp_server.addr
|
96
|
+
Addr['127.0.0.1', udp_client.addr[1], :UDP]
|
97
|
+
end
|
98
|
+
run_all_threads
|
99
|
+
log_lines = addrs.map { |addr| "received #{addr} unknown message type" }
|
100
|
+
assert log_lines.any? { |line| logged.include?(line) }
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'logs packet dispatches' do
|
104
|
+
server = UDPSocket.new.tap { |s| s.bind '127.0.0.1', 0 }
|
105
|
+
addr = Addr[server.addr[3], server.addr[1], :UDP]
|
106
|
+
NetDispatcher.dispatch addr, Message.parse('PING')
|
107
|
+
logged.must_include "sent #{addr} 50 49 4e 47"
|
108
|
+
end
|
78
109
|
end
|
79
110
|
end
|
80
|
-
end
|
111
|
+
end
|