kamerling 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|