kamerling 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.
- checksums.yaml +7 -0
- data/.rubocop.yml +50 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +90 -0
- data/LICENCE +661 -0
- data/README.md +6 -0
- data/Rakefile +18 -0
- data/bin/kamerling +9 -0
- data/config/reek.yml +18 -0
- data/kamerling.gemspec +30 -0
- data/lib/kamerling/addr.rb +18 -0
- data/lib/kamerling/client.rb +2 -0
- data/lib/kamerling/core_extensions/main.rb +17 -0
- data/lib/kamerling/core_extensions.rb +1 -0
- data/lib/kamerling/handler.rb +24 -0
- data/lib/kamerling/http_api.rb +38 -0
- data/lib/kamerling/logging.rb +20 -0
- data/lib/kamerling/message.rb +47 -0
- data/lib/kamerling/migrations/1_basic_schema.rb +45 -0
- data/lib/kamerling/net_dispatcher.rb +8 -0
- data/lib/kamerling/project.rb +2 -0
- data/lib/kamerling/receiver.rb +11 -0
- data/lib/kamerling/registrar.rb +9 -0
- data/lib/kamerling/registration.rb +2 -0
- data/lib/kamerling/repo.rb +32 -0
- data/lib/kamerling/repos.rb +65 -0
- data/lib/kamerling/result.rb +2 -0
- data/lib/kamerling/server/http.rb +26 -0
- data/lib/kamerling/server/sock.rb +32 -0
- data/lib/kamerling/server/tcp.rb +19 -0
- data/lib/kamerling/server/udp.rb +20 -0
- data/lib/kamerling/server_runner.rb +50 -0
- data/lib/kamerling/task.rb +2 -0
- data/lib/kamerling/task_dispatcher.rb +29 -0
- data/lib/kamerling/uuid.rb +17 -0
- data/lib/kamerling/uuid_object.rb +79 -0
- data/lib/kamerling/views/clients.slim +6 -0
- data/lib/kamerling/views/layout.slim +6 -0
- data/lib/kamerling/views/project.slim +11 -0
- data/lib/kamerling/views/projects.slim +9 -0
- data/lib/kamerling/views/root.slim +6 -0
- data/lib/kamerling.rb +29 -0
- data/spec/kamerling/addr_spec.rb +27 -0
- data/spec/kamerling/client_spec.rb +9 -0
- data/spec/kamerling/core_extensions/main_spec.rb +18 -0
- data/spec/kamerling/handler_spec.rb +30 -0
- data/spec/kamerling/http_api_spec.rb +93 -0
- data/spec/kamerling/logging_spec.rb +73 -0
- data/spec/kamerling/message_spec.rb +70 -0
- data/spec/kamerling/net_dispatcher_spec.rb +21 -0
- data/spec/kamerling/receiver_spec.rb +22 -0
- data/spec/kamerling/registrar_spec.rb +16 -0
- data/spec/kamerling/repo_spec.rb +60 -0
- data/spec/kamerling/repos_spec.rb +136 -0
- data/spec/kamerling/server/http_spec.rb +22 -0
- data/spec/kamerling/server/tcp_spec.rb +46 -0
- data/spec/kamerling/server/udp_spec.rb +44 -0
- data/spec/kamerling/server_runner_spec.rb +65 -0
- data/spec/kamerling/task_dispatcher_spec.rb +23 -0
- data/spec/kamerling/task_spec.rb +9 -0
- data/spec/kamerling/uuid_object_spec.rb +101 -0
- data/spec/kamerling/uuid_spec.rb +24 -0
- data/spec/spec_helper.rb +26 -0
- metadata +325 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
module Kamerling
|
2
|
+
def self.UUIDObject *params
|
3
|
+
class_definition_from attrs_from params
|
4
|
+
end
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def self.attrs_from params
|
9
|
+
{ uuid: -> { UUID.new } }.tap do |attrs|
|
10
|
+
attrs.merge! params.pop if params.last.is_a? Hash
|
11
|
+
attrs.merge! raises_from params
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.class_definition_from attrs
|
16
|
+
Class.new do
|
17
|
+
define_singleton_method(:attrs) { attrs }
|
18
|
+
|
19
|
+
def self.from_h hash, repos: Repos
|
20
|
+
args = hash.reduce({}) do |result, (key, _)|
|
21
|
+
result.merge from_h_mapping hash, key, repos
|
22
|
+
end
|
23
|
+
new args
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize args = {}
|
27
|
+
@values = Hash[self.class.attrs.map do |attr, default|
|
28
|
+
value = args.fetch attr do
|
29
|
+
default.respond_to?(:call) ? default.call : default
|
30
|
+
end
|
31
|
+
[attr, value]
|
32
|
+
end]
|
33
|
+
end
|
34
|
+
|
35
|
+
def == other
|
36
|
+
uuid == other.uuid
|
37
|
+
end
|
38
|
+
|
39
|
+
attrs.each do |attr, _|
|
40
|
+
define_method(attr) { @values[attr] }
|
41
|
+
define_method("#{attr}=") { |val| @values[attr] = val }
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_h
|
45
|
+
self.class.attrs.reduce({}) do |hash, (attr, _)|
|
46
|
+
hash.merge to_h_mapping attr
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def self.from_h_mapping hash, key, repos
|
53
|
+
case key
|
54
|
+
when :host, :port, :prot
|
55
|
+
{ addr: Addr[hash[:host], hash[:port], hash[:prot].to_sym] }
|
56
|
+
when :client_uuid then { client: repos[Client][hash[key]] }
|
57
|
+
when :project_uuid then { project: repos[Project][hash[key]] }
|
58
|
+
when :task_uuid then { task: repos[Task][hash[key]] }
|
59
|
+
else { key => hash[key] }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_h_mapping attr
|
64
|
+
case value = @values[attr]
|
65
|
+
when Addr
|
66
|
+
{ host: value.host, port: value.port, prot: value.prot.to_s }
|
67
|
+
when Client then { client_uuid: client.uuid }
|
68
|
+
when Project then { project_uuid: project.uuid }
|
69
|
+
when Task then { task_uuid: task.uuid }
|
70
|
+
else { attr => value }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.raises_from params
|
77
|
+
Hash[params.map { |param| [param, -> { raise "#{param} required" }] }]
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
nav
|
2
|
+
ul#clients
|
3
|
+
- clients.each do |client|
|
4
|
+
li
|
5
|
+
a data-busy=client.busy.to_s data-uuid=client.uuid href="/clients/#{client.uuid}" rel='client'
|
6
|
+
= client.busy
|
7
|
+
ul#tasks
|
8
|
+
- tasks.each do |task|
|
9
|
+
li
|
10
|
+
a data-done=task.done.to_s data-uuid=task.uuid href="/tasks/#{task.uuid}" rel='task'
|
11
|
+
= task.done
|
data/lib/kamerling.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
require_relative 'kamerling/core_extensions'
|
4
|
+
|
5
|
+
include Kamerling::CoreExtensions::Main
|
6
|
+
|
7
|
+
require_relative 'kamerling/addr'
|
8
|
+
require_relative 'kamerling/handler'
|
9
|
+
require_relative 'kamerling/message'
|
10
|
+
require_relative 'kamerling/net_dispatcher'
|
11
|
+
require_relative 'kamerling/receiver'
|
12
|
+
require_relative 'kamerling/registrar'
|
13
|
+
require_relative 'kamerling/repo'
|
14
|
+
require_relative 'kamerling/repos'
|
15
|
+
require_relative 'kamerling/http_api'
|
16
|
+
require_relative 'kamerling/server/http'
|
17
|
+
require_relative 'kamerling/server/sock'
|
18
|
+
require_relative 'kamerling/server/tcp'
|
19
|
+
require_relative 'kamerling/server/udp'
|
20
|
+
require_relative 'kamerling/server_runner'
|
21
|
+
require_relative 'kamerling/task_dispatcher'
|
22
|
+
require_relative 'kamerling/uuid'
|
23
|
+
require_relative 'kamerling/uuid_object'
|
24
|
+
require_relative 'kamerling/client'
|
25
|
+
require_relative 'kamerling/project'
|
26
|
+
require_relative 'kamerling/registration'
|
27
|
+
require_relative 'kamerling/result'
|
28
|
+
require_relative 'kamerling/task'
|
29
|
+
require_relative 'kamerling/logging'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Addr do
|
4
|
+
let(:addr) { Addr['localhost', 1981, :TCP] }
|
5
|
+
|
6
|
+
describe '#connectable?' do
|
7
|
+
it 'is a predicate whether the (TCP) address is connectable' do
|
8
|
+
server = TCPServer.new(*addr)
|
9
|
+
addr.must_be :connectable?
|
10
|
+
server.close
|
11
|
+
addr.wont_be :connectable?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#to_a' do
|
16
|
+
it 'returns host + port for splat use' do
|
17
|
+
splat = *addr
|
18
|
+
splat.must_equal ['localhost', 1981]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#to_s' do
|
23
|
+
it 'returns the Addr in ‘host:port (protocol)’ notation' do
|
24
|
+
addr.to_s.must_equal 'localhost:1981 (TCP)'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe CoreExtensions::Main do
|
4
|
+
describe '#req' do
|
5
|
+
it 'raises a RuntimeError that a parameter is required' do
|
6
|
+
-> { CoreExtensions::Main.req(:foo) }.must_raise(RuntimeError)
|
7
|
+
.message.must_include 'param foo is required'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#warn_off' do
|
12
|
+
it 'turns $VERBOSE off inside the block' do
|
13
|
+
assert $VERBOSE
|
14
|
+
CoreExtensions::Main.warn_off { refute $VERBOSE }
|
15
|
+
assert $VERBOSE
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Handler do
|
4
|
+
describe '#handle' do
|
5
|
+
fakes :addr, :receiver, :registrar
|
6
|
+
let(:handler) { Handler.new receiver: receiver, registrar: registrar }
|
7
|
+
|
8
|
+
it 'handles RGST inputs' do
|
9
|
+
input = 'RGST' + "\0" * 12 + '16B client UUID16B project UUID'
|
10
|
+
handler.handle input, addr
|
11
|
+
registrar.must_have_received :register, [{ addr: addr,
|
12
|
+
client_uuid: UUID['16B client UUID'],
|
13
|
+
project_uuid: UUID['16B project UUID'] }]
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'handles RSLT inputs' do
|
17
|
+
input = 'RSLT' + "\0" * 12
|
18
|
+
input << '16B client UUID16B project UUID16B task UUIDdata'
|
19
|
+
handler.handle input, addr
|
20
|
+
receiver.must_have_received :receive, [{ addr: addr,
|
21
|
+
client_uuid: UUID['16B client UUID'], data: 'data',
|
22
|
+
task_uuid: UUID['16B task UUID'] }]
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'raises on unknown inputs' do
|
26
|
+
ex = -> { handler.handle 'MESS', addr }.must_raise Handler::UnknownInput
|
27
|
+
ex.message.must_equal 'MESS'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
require_relative '../spec_helper'
|
4
|
+
|
5
|
+
module Kamerling describe HTTPAPI do
|
6
|
+
let(:app) { HTTPAPI.set repos: repos }
|
7
|
+
let(:doc) { Nokogiri::HTML last_response.body }
|
8
|
+
let(:ecc) { fake :project, uuid: UUID.new }
|
9
|
+
let(:gimps) { fake :project, uuid: UUID.new }
|
10
|
+
let(:repos) { fake :repos, as: :class, projects: [gimps, ecc] }
|
11
|
+
|
12
|
+
describe 'GET /' do
|
13
|
+
it 'contains links to clients and projects' do
|
14
|
+
get '/'
|
15
|
+
doc.at('#clients')['href'].must_equal '/clients'
|
16
|
+
doc.at('#projects')['href'].must_equal '/projects'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'GET /clients' do
|
21
|
+
it 'contains links to and UUIDs of clients' do
|
22
|
+
fpga = fake :client, uuid: UUID.new
|
23
|
+
stub(repos).clients { [fpga] }
|
24
|
+
get '/clients'
|
25
|
+
links = doc.css '#clients a[rel=client]'
|
26
|
+
links.size.must_equal 1
|
27
|
+
links.at("[data-uuid='#{fpga.uuid}']")['href']
|
28
|
+
.must_equal "/clients/#{fpga.uuid}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'GET /projects' do
|
33
|
+
it 'contains links to and UUIDs of projects' do
|
34
|
+
get '/projects'
|
35
|
+
links = doc.css '#projects a[rel=project]'
|
36
|
+
links.size.must_equal 2
|
37
|
+
links.at("[data-uuid='#{gimps.uuid}']")['href']
|
38
|
+
.must_equal "/projects/#{gimps.uuid}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'GET /projects/{uuid}' do
|
43
|
+
let(:cpu) { fake :client, busy: false, uuid: UUID.new }
|
44
|
+
let(:gpu) { fake :client, busy: true, uuid: UUID.new }
|
45
|
+
let(:three) { fake :task, done: false, uuid: UUID.new }
|
46
|
+
let(:seven) { fake :task, done: true, uuid: UUID.new }
|
47
|
+
|
48
|
+
before do
|
49
|
+
stub(repos).project(gimps.uuid) { gimps }
|
50
|
+
stub(repos).clients_for(gimps) { [cpu, gpu] }
|
51
|
+
stub(repos).tasks_for(gimps) { [three, seven] }
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'contains links to and info on the project’s clients' do
|
55
|
+
get "/projects/#{gimps.uuid}"
|
56
|
+
links = doc.css '#clients a[rel=client]'
|
57
|
+
links.size.must_equal 2
|
58
|
+
links.at("[data-uuid='#{cpu.uuid}']")['href']
|
59
|
+
.must_equal "/clients/#{cpu.uuid}"
|
60
|
+
links.at("[data-uuid='#{cpu.uuid}']")['data-busy'].must_equal 'false'
|
61
|
+
links.at("[data-uuid='#{gpu.uuid}']")['data-busy'].must_equal 'true'
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'contains links to and info on the project’s tasks' do
|
65
|
+
get "/projects/#{gimps.uuid}"
|
66
|
+
links = doc.css '#tasks a[rel=task]'
|
67
|
+
links.size.must_equal 2
|
68
|
+
links.at("[data-uuid='#{three.uuid}']")['href']
|
69
|
+
.must_equal "/tasks/#{three.uuid}"
|
70
|
+
links.at("[data-uuid='#{three.uuid}']")['data-done'].must_equal 'false'
|
71
|
+
links.at("[data-uuid='#{seven.uuid}']")['data-done'].must_equal 'true'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'POST /projects' do
|
76
|
+
it 'creates a new project with the given name and UUID' do
|
77
|
+
post '/projects', name: 'ECC', uuid: uuid = UUID.new
|
78
|
+
repos.must_have_received :<<, [Project.new(name: 'ECC', uuid: uuid)]
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'creates a new project with a random UUID if missing' do
|
82
|
+
post '/projects', name: 'ECC'
|
83
|
+
project = Project.new name: 'ECC', uuid: any(String)
|
84
|
+
repos.must_have_received :<<, [project]
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'redirects to /projects' do
|
88
|
+
post '/projects', name: 'ECC'
|
89
|
+
follow_redirect!
|
90
|
+
URI(last_request.url).path.must_equal '/projects'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Logging do
|
4
|
+
let(:logged) { stream.tap(&:rewind).read }
|
5
|
+
let(:logger) { Logger.new stream }
|
6
|
+
let(:stream) { StringIO.new }
|
7
|
+
let(:tcp_server) { Server::TCP.new addr: Addr['localhost', 1981, :TCP] }
|
8
|
+
let(:udp_server) { Server::UDP.new addr: Addr['localhost', 1979, :UDP] }
|
9
|
+
|
10
|
+
before do
|
11
|
+
Logging.log_to logger: logger
|
12
|
+
tcp_server.start
|
13
|
+
udp_server.start
|
14
|
+
end
|
15
|
+
|
16
|
+
after do
|
17
|
+
tcp_server.stop
|
18
|
+
udp_server.stop
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '.new' do
|
22
|
+
it 'logs TCP server starts' do
|
23
|
+
logged.must_include 'start localhost:1981 (TCP)'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'logs TCP server stops' do
|
27
|
+
tcp_server.stop
|
28
|
+
logged.must_include 'stop localhost:1981 (TCP)'
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'logs TCP server connects' do
|
32
|
+
tcp_addr = TCPSocket.open(*tcp_server.addr) do |socket|
|
33
|
+
Addr[*socket.local_address.ip_unpack, :TCP]
|
34
|
+
end
|
35
|
+
run_all_threads
|
36
|
+
logged.must_include "connect #{tcp_addr}"
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'logs TCP server receives' do
|
40
|
+
tcp_addr = TCPSocket.open(*tcp_server.addr) do |socket|
|
41
|
+
socket << 'PING'
|
42
|
+
Addr[*socket.local_address.ip_unpack, :TCP]
|
43
|
+
end
|
44
|
+
run_all_threads
|
45
|
+
logged.must_include "received #{tcp_addr} PING"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'logs UDP server starts' do
|
49
|
+
logged.must_include 'start localhost:1979 (UDP)'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'logs UDP server stops' do
|
53
|
+
udp_server.stop
|
54
|
+
logged.must_include 'stop localhost:1979 (UDP)'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'logs UDP server connects' do
|
58
|
+
udp_client = UDPSocket.new
|
59
|
+
udp_client.send 'PING', 0, *udp_server.addr
|
60
|
+
udp_addr = Addr['127.0.0.1', udp_client.addr[1], :UDP]
|
61
|
+
run_all_threads
|
62
|
+
logged.must_include "connect #{udp_addr}"
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'logs TCP server receives' do
|
66
|
+
udp_client = UDPSocket.new
|
67
|
+
udp_client.send 'PING', 0, *udp_server.addr
|
68
|
+
udp_addr = Addr['127.0.0.1', udp_client.addr[1], :UDP]
|
69
|
+
run_all_threads
|
70
|
+
logged.must_include "received #{udp_addr} PING"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Message do
|
4
|
+
let(:mess) do
|
5
|
+
Message.new "DATA\0\0\0\0\0\0\0\0\0\0\0\0" \
|
6
|
+
'16B client UUID16B project UUID16B task UUIDsome payload'
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '.[]' do
|
10
|
+
it 'constructs a new message' do
|
11
|
+
client = fake :client, uuid: UUID.new
|
12
|
+
project = fake :project, uuid: UUID.new
|
13
|
+
task = fake :task, uuid: UUID.new
|
14
|
+
message = Message[client: client, payload: 'pay', project: project,
|
15
|
+
task: task, type: :DATA]
|
16
|
+
message.client_uuid.must_equal client.uuid
|
17
|
+
message.project_uuid.must_equal project.uuid
|
18
|
+
message.task_uuid.must_equal task.uuid
|
19
|
+
message.payload.must_equal 'pay'
|
20
|
+
message.type.must_equal :DATA
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.new' do
|
25
|
+
it 'raises on unknown message types' do
|
26
|
+
-> { Message.new 'MESS age' }.must_raise Message::UnknownType
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'doesn’t raise on empty messages' do
|
30
|
+
Message.new ''
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#client_uuid' do
|
35
|
+
it 'returns the client UUID' do
|
36
|
+
mess.client_uuid.must_equal '31364220-636c-6965-6e74-202055554944'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#payload' do
|
41
|
+
it 'returns the result payload' do
|
42
|
+
mess.payload.must_equal 'some payload'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#project_uuid' do
|
47
|
+
it 'returns the project UUID' do
|
48
|
+
mess.project_uuid.must_equal '31364220-7072-6f6a-6563-742055554944'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#task_uuid' do
|
53
|
+
it 'returns the task UUID' do
|
54
|
+
mess.task_uuid.must_equal '31364220-7461-736b-2020-202055554944'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#to_s' do
|
59
|
+
it 'returns the raw bytes' do
|
60
|
+
mess.to_s.must_equal "#{mess.type}\0\0\0\0\0\0\0\0\0\0\0\0" +
|
61
|
+
'16B client UUID16B project UUID16B task UUIDsome payload'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#type' do
|
66
|
+
it 'returns the message type' do
|
67
|
+
mess.type.must_match(/\A[A-Z]{4}\z/)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe NetDispatcher do
|
4
|
+
describe '#dispatch' do
|
5
|
+
it 'dispatches messages to TCP clients' do
|
6
|
+
server = TCPServer.open 0
|
7
|
+
thread = Thread.new { server.accept.read }
|
8
|
+
addr = Addr[server.addr[3], server.addr[1], :TCP]
|
9
|
+
NetDispatcher.new.dispatch addr, 'foo'
|
10
|
+
thread.value.must_equal 'foo'
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'dispatches messages to UDP clients' do
|
14
|
+
server = UDPSocket.new.tap { |s| s.bind '127.0.0.1', 0 }
|
15
|
+
thread = Thread.new { server.recvfrom(2**16).first }
|
16
|
+
addr = Addr[server.addr[3], server.addr[1], :UDP]
|
17
|
+
NetDispatcher.new.dispatch addr, 'foo'
|
18
|
+
thread.value.must_equal 'foo'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Receiver do
|
4
|
+
describe '#receive' do
|
5
|
+
fakes :addr, :client, :task
|
6
|
+
|
7
|
+
it 'saves the result and updates client and task' do
|
8
|
+
repos = fake :repos, as: :class
|
9
|
+
stub(repos).<<(any_args) { repos }
|
10
|
+
stub(repos).[](Client) { fake :repo, :[] => client }
|
11
|
+
stub(repos).[](Task) { fake :repo, :[] => task }
|
12
|
+
Receiver.new.receive addr: addr, client_uuid: client.uuid, data: 'data',
|
13
|
+
repos: repos, task_uuid: task.uuid
|
14
|
+
client.must_have_received :busy=, [false]
|
15
|
+
task.must_have_received :done=, [true]
|
16
|
+
repos.must_have_received :<<, [client]
|
17
|
+
repos.must_have_received :<<, [task]
|
18
|
+
repos.must_have_received :<<, [Result.new(addr: addr, client: client,
|
19
|
+
data: 'data', task: task, uuid: anything)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Registrar do
|
4
|
+
describe '#register' do
|
5
|
+
fakes :addr, :client, :project, :repo
|
6
|
+
|
7
|
+
it 'registers that the given client can do the given project' do
|
8
|
+
repos = { Client => { client.uuid => client },
|
9
|
+
Project => { project.uuid => project }, Registration => repo }
|
10
|
+
Registrar.new.register addr: addr, client_uuid: client.uuid,
|
11
|
+
project_uuid: project.uuid, repos: repos
|
12
|
+
repo.must_have_received :<<, [Registration.new(addr: addr,
|
13
|
+
client: client, project: project, uuid: anything)]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
module Kamerling describe Repo do
|
4
|
+
Tune = Kamerling.UUIDObject :genre
|
5
|
+
|
6
|
+
describe '#<<' do
|
7
|
+
it 'passes the Hash version of an object to the source' do
|
8
|
+
tune = Tune.new genre: :chap_hop
|
9
|
+
source = fake Sequel::Dataset
|
10
|
+
mock(source) << { genre: :chap_hop, uuid: tune.uuid }
|
11
|
+
Repo.new(Tune, source) << tune
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'updates the source’s version if it exists there' do
|
15
|
+
dataset = fake Sequel::Dataset
|
16
|
+
source = fake Sequel::Dataset
|
17
|
+
tune = Tune.new genre: :chap_hop
|
18
|
+
stub(source).<<(tune.to_h) { raise Sequel::UniqueConstraintViolation }
|
19
|
+
stub(source).where(uuid: tune.uuid) { dataset }
|
20
|
+
Repo.new(Tune, source) << tune
|
21
|
+
dataset.must_have_received :update, [tune.to_h]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#[]' do
|
26
|
+
it 'hydrates the object found in the repo' do
|
27
|
+
uuid = UUID.new
|
28
|
+
source = { { uuid: uuid } => { genre: :chap_hop, uuid: uuid } }
|
29
|
+
Repo.new(Tune, source)[uuid]
|
30
|
+
.must_equal Tune.new genre: :chap_hop, uuid: uuid
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'raises NotFound if the object is not found in the repo' do
|
34
|
+
-> { Repo.new(Tune, {})[UUID.new] }.must_raise Repo::NotFound
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#all' do
|
39
|
+
it 'returns all objects' do
|
40
|
+
tune = Tune.new genre: :chap_hop, uuid: UUID.new
|
41
|
+
source = fake Sequel::Dataset,
|
42
|
+
all: [{ genre: :chap_hop, uuid: tune.uuid }]
|
43
|
+
Repo.new(Tune, source).all.must_equal [tune]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#related_to' do
|
48
|
+
it 'returns objects related to the given object' do
|
49
|
+
tunes = [Tune.new(genre: :ragga), Tune.new(genre: :reggae)]
|
50
|
+
project = fake :project, uuid: UUID.new
|
51
|
+
results = [
|
52
|
+
{ genre: :ragga, uuid: tunes.first.uuid },
|
53
|
+
{ genre: :reggae, uuid: tunes.last.uuid },
|
54
|
+
]
|
55
|
+
source = fake Sequel::Dataset
|
56
|
+
stub(source).where(project_uuid: project.uuid) { results }
|
57
|
+
Repo.new(Tune, source).related_to(project).must_equal tunes
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end end
|