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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +50 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +90 -0
  6. data/LICENCE +661 -0
  7. data/README.md +6 -0
  8. data/Rakefile +18 -0
  9. data/bin/kamerling +9 -0
  10. data/config/reek.yml +18 -0
  11. data/kamerling.gemspec +30 -0
  12. data/lib/kamerling/addr.rb +18 -0
  13. data/lib/kamerling/client.rb +2 -0
  14. data/lib/kamerling/core_extensions/main.rb +17 -0
  15. data/lib/kamerling/core_extensions.rb +1 -0
  16. data/lib/kamerling/handler.rb +24 -0
  17. data/lib/kamerling/http_api.rb +38 -0
  18. data/lib/kamerling/logging.rb +20 -0
  19. data/lib/kamerling/message.rb +47 -0
  20. data/lib/kamerling/migrations/1_basic_schema.rb +45 -0
  21. data/lib/kamerling/net_dispatcher.rb +8 -0
  22. data/lib/kamerling/project.rb +2 -0
  23. data/lib/kamerling/receiver.rb +11 -0
  24. data/lib/kamerling/registrar.rb +9 -0
  25. data/lib/kamerling/registration.rb +2 -0
  26. data/lib/kamerling/repo.rb +32 -0
  27. data/lib/kamerling/repos.rb +65 -0
  28. data/lib/kamerling/result.rb +2 -0
  29. data/lib/kamerling/server/http.rb +26 -0
  30. data/lib/kamerling/server/sock.rb +32 -0
  31. data/lib/kamerling/server/tcp.rb +19 -0
  32. data/lib/kamerling/server/udp.rb +20 -0
  33. data/lib/kamerling/server_runner.rb +50 -0
  34. data/lib/kamerling/task.rb +2 -0
  35. data/lib/kamerling/task_dispatcher.rb +29 -0
  36. data/lib/kamerling/uuid.rb +17 -0
  37. data/lib/kamerling/uuid_object.rb +79 -0
  38. data/lib/kamerling/views/clients.slim +6 -0
  39. data/lib/kamerling/views/layout.slim +6 -0
  40. data/lib/kamerling/views/project.slim +11 -0
  41. data/lib/kamerling/views/projects.slim +9 -0
  42. data/lib/kamerling/views/root.slim +6 -0
  43. data/lib/kamerling.rb +29 -0
  44. data/spec/kamerling/addr_spec.rb +27 -0
  45. data/spec/kamerling/client_spec.rb +9 -0
  46. data/spec/kamerling/core_extensions/main_spec.rb +18 -0
  47. data/spec/kamerling/handler_spec.rb +30 -0
  48. data/spec/kamerling/http_api_spec.rb +93 -0
  49. data/spec/kamerling/logging_spec.rb +73 -0
  50. data/spec/kamerling/message_spec.rb +70 -0
  51. data/spec/kamerling/net_dispatcher_spec.rb +21 -0
  52. data/spec/kamerling/receiver_spec.rb +22 -0
  53. data/spec/kamerling/registrar_spec.rb +16 -0
  54. data/spec/kamerling/repo_spec.rb +60 -0
  55. data/spec/kamerling/repos_spec.rb +136 -0
  56. data/spec/kamerling/server/http_spec.rb +22 -0
  57. data/spec/kamerling/server/tcp_spec.rb +46 -0
  58. data/spec/kamerling/server/udp_spec.rb +44 -0
  59. data/spec/kamerling/server_runner_spec.rb +65 -0
  60. data/spec/kamerling/task_dispatcher_spec.rb +23 -0
  61. data/spec/kamerling/task_spec.rb +9 -0
  62. data/spec/kamerling/uuid_object_spec.rb +101 -0
  63. data/spec/kamerling/uuid_spec.rb +24 -0
  64. data/spec/spec_helper.rb +26 -0
  65. metadata +325 -0
@@ -0,0 +1,136 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe Repos do
4
+ describe '.<<' do
5
+ it 'shuffles the object into the right repo' do
6
+ Repos.repos = { Object => repo = fake(:repo) }
7
+ Repos.<< object = Object.new
8
+ repo.must_have_received :<<, [object]
9
+ end
10
+
11
+ it 'can be chained' do
12
+ str_repo, sym_repo = fake(:repo), fake(:repo)
13
+ Repos.repos = { String => str_repo, Symbol => sym_repo }
14
+ Repos << 'str' << :sym
15
+ str_repo.must_have_received :<<, ['str']
16
+ sym_repo.must_have_received :<<, [:sym]
17
+ end
18
+ end
19
+
20
+ describe '.[]' do
21
+ it 'allows querying for repository objects' do
22
+ client = fake :client, uuid: UUID.new
23
+ Repos.repos = { Client => {} }
24
+ Repos[Client][client.uuid].must_be_nil
25
+ Repos.repos = { Client => { client.uuid => client } }
26
+ Repos[Client][client.uuid].must_equal client
27
+ end
28
+ end
29
+
30
+ describe '.clients' do
31
+ it 'returns all clients' do
32
+ Repos.repos = { Client => fake(:repo, all: all_clients = fake) }
33
+ Repos.clients.must_equal all_clients
34
+ end
35
+ end
36
+
37
+ describe '.clients_for' do
38
+ it 'returns all clients for the given project' do
39
+ clients = [fake(:client), fake(:client)]
40
+ project = fake :project
41
+ regs = clients.map { |client| fake :registration, client: client }
42
+ reg_repo = fake :repo
43
+ stub(reg_repo).related_to(project) { regs }
44
+ Repos.repos = { Registration => reg_repo }
45
+ Repos.clients_for(project).must_equal clients
46
+ end
47
+ end
48
+
49
+ describe '.db=' do
50
+ it 'auto-migrates the passed db' do
51
+ db = Sequel.sqlite
52
+ warn_off { db.tables.wont_include :schema_info }
53
+ Repos.db = db
54
+ warn_off { db.tables.must_include :schema_info }
55
+ end
56
+ end
57
+
58
+ describe '.free_clients_for' do
59
+ it 'returns free clients for the given project' do
60
+ busy_client = fake :client, busy: true
61
+ free_client = fake :client, busy: false
62
+ busy_reg = fake :registration, client: busy_client
63
+ free_reg = fake :registration, client: free_client
64
+ project = fake :project
65
+ repo = fake :repo
66
+ stub(repo).related_to(project) { [busy_reg, free_reg] }
67
+ Repos.repos = { Registration => repo }
68
+ Repos.free_clients_for(project).must_equal [free_client]
69
+ end
70
+ end
71
+
72
+ describe '.next_task_for' do
73
+ it 'returns the next task for the given project' do
74
+ project = fake :project
75
+ done_task = fake :task, done: true
76
+ new_task = fake :task, done: false
77
+ repo = fake :repo
78
+ stub(repo).related_to(project) { [done_task, new_task] }
79
+ Repos.repos = { Task => repo }
80
+ Repos.next_task_for(project).must_equal new_task
81
+ end
82
+ end
83
+
84
+ describe '.project' do
85
+ it 'returns the project with the given UUID' do
86
+ gimps = fake :project, uuid: UUID.new
87
+ Repos.repos = { Project => { gimps.uuid => gimps } }
88
+ Repos.project(gimps.uuid).must_equal gimps
89
+ end
90
+ end
91
+
92
+ describe '.projects' do
93
+ it 'returns all projects' do
94
+ Repos.repos = { Project => fake(:repo, all: all_projects = fake) }
95
+ Repos.projects.must_equal all_projects
96
+ end
97
+ end
98
+
99
+ describe '.tasks_for' do
100
+ it 'returns all tasks for the given project' do
101
+ project = fake :project
102
+ tasks = [fake(:task), fake(:task)]
103
+ task_repo = fake :repo
104
+ stub(task_repo).related_to(project) { tasks }
105
+ Repos.repos = { Task => task_repo }
106
+ Repos.tasks_for(project).must_equal tasks
107
+ end
108
+ end
109
+
110
+ describe 'when working on actual database' do
111
+ before { Repos.db = Sequel.sqlite }
112
+
113
+ it 'makes sure objects can be stored and retrieved' do
114
+ addr = Addr['127.0.0.1', 1981, :TCP]
115
+ client = Client.new addr: addr, uuid: UUID.new
116
+ project = Project.new name: 'project name', uuid: UUID.new
117
+ task = Task.new data: 'data', project: project, uuid: UUID.new
118
+ reg = Registration.new addr: addr, client: client, project: project
119
+ res = Result.new addr: addr, client: client, data: 'da', task: task
120
+ Repos << client << project << task << reg << res
121
+ Repos[Client][client.uuid].must_equal client
122
+ Repos[Project][project.uuid].must_equal project
123
+ Repos[Registration][reg.uuid].must_equal reg
124
+ Repos[Result][res.uuid].must_equal res
125
+ Repos[Task][task.uuid].must_equal task
126
+ end
127
+
128
+ it 'makes sure objects can be updated' do
129
+ client = Client.new addr: Addr['127.0.0.1', 1979, :TCP], uuid: UUID.new
130
+ Repos << client
131
+ client.addr.port = 1981
132
+ Repos << client
133
+ Repos[Client][client.uuid].addr.port.must_equal 1981
134
+ end
135
+ end
136
+ end end
@@ -0,0 +1,22 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Kamerling describe Server::HTTP do
4
+ let(:addr) { Addr['localhost', 2009, :TCP] }
5
+
6
+ describe '#addr' do
7
+ it 'returns the server’s host + port as a TCP addr' do
8
+ Server::HTTP.new(addr: addr).addr.must_equal addr
9
+ end
10
+ end
11
+
12
+ describe '#start, #stop' do
13
+ it 'starts/stops a HTTP server on the given host and port' do
14
+ capture_io do
15
+ server = Server::HTTP.new(addr: addr).start
16
+ uri = URI.parse 'http://localhost:2009'
17
+ Net::HTTP.get_response(uri).must_be_kind_of Net::HTTPSuccess
18
+ server.stop
19
+ end
20
+ end
21
+ end
22
+ end end
@@ -0,0 +1,46 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Kamerling describe Server::TCP do
4
+ let(:addr) { Addr['localhost', 1981, :TCP] }
5
+
6
+ describe '#addr' do
7
+ it 'returns the server’s host + port as a TCP addr' do
8
+ Server::TCP.new(addr: addr).addr.must_equal addr
9
+ end
10
+ end
11
+
12
+ describe '#start' do
13
+ it 'listens on a TCP port and passes received inputs to the handler' do
14
+ server = Server::TCP.new addr: addr, handler: handler = fake(:handler)
15
+ server.start
16
+ s_addr_foo = TCPSocket.open(*server.addr) do |socket|
17
+ socket << 'foo'
18
+ Addr[*socket.local_address.ip_unpack, :TCP]
19
+ end
20
+ s_addr_bar = TCPSocket.open(*server.addr) do |socket|
21
+ socket << 'bar'
22
+ Addr[*socket.local_address.ip_unpack, :TCP]
23
+ end
24
+ run_all_threads
25
+ server.stop
26
+ handler.must_have_received :handle, ['foo', s_addr_foo]
27
+ handler.must_have_received :handle, ['bar', s_addr_bar]
28
+ end
29
+
30
+ it 'doesn’t blow up on unknown inputs' do
31
+ server = Server::TCP.new addr: addr, handler: handler = fake(:handler)
32
+ server.start
33
+ stub(handler).handle('foo', any(Addr)) { raise Handler::UnknownInput }
34
+ TCPSocket.open(*server.addr) { |socket| socket << 'foo' }
35
+ server.stop
36
+ end
37
+ end
38
+
39
+ describe '#stop' do
40
+ it 'stops the server' do
41
+ server = Server::TCP.new(addr: addr).start
42
+ server.stop
43
+ -> { TCPSocket.open(*addr).close }.must_raise Errno::ECONNREFUSED
44
+ end
45
+ end
46
+ end end
@@ -0,0 +1,44 @@
1
+ require_relative '../../spec_helper'
2
+
3
+ module Kamerling describe Server::UDP do
4
+ let(:addr) { Addr['localhost', 1979, :UDP] }
5
+
6
+ describe '#start' do
7
+ it 'listens on an UDP port and passes received inputs to the handler' do
8
+ server = Server::UDP.new addr: addr, handler: handler = fake(:handler)
9
+ server.start
10
+ foo = UDPSocket.new
11
+ bar = UDPSocket.new
12
+ foo.send 'foo', 0, *server.addr
13
+ bar.send 'bar', 0, *server.addr
14
+ foo_addr = Addr['127.0.0.1', foo.addr[1], :UDP]
15
+ bar_addr = Addr['127.0.0.1', bar.addr[1], :UDP]
16
+ run_all_threads
17
+ server.stop
18
+ handler.must_have_received :handle, ['foo', foo_addr]
19
+ handler.must_have_received :handle, ['bar', bar_addr]
20
+ end
21
+
22
+ it 'doesn’t blow up on unknown inputs' do
23
+ server = Server::UDP.new addr: addr, handler: handler = fake(:handler)
24
+ server.start
25
+ stub(handler).handle('foo', any(Addr)) { raise Handler::UnknownInput }
26
+ UDPSocket.new.send 'foo', 0, *server.addr
27
+ run_all_threads
28
+ server.stop
29
+ end
30
+ end
31
+
32
+ describe '#stop' do
33
+ it 'closes the socket (and thus allows rebinding to it)' do
34
+ Server::UDP.new(addr: addr).start.stop
35
+ UDPSocket.new.tap { |socket| socket.bind(*addr) }.close
36
+ end
37
+ end
38
+
39
+ describe '#addr' do
40
+ it 'returns the server’s host + port as an UDP addr' do
41
+ Server::UDP.new(addr: addr).addr.must_equal addr
42
+ end
43
+ end
44
+ end end
@@ -0,0 +1,65 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe ServerRunner do
4
+ let(:http) { fake { Server::HTTP } }
5
+ let(:tcp) { fake { Server::TCP } }
6
+ let(:udp) { fake { Server::UDP } }
7
+ let(:http_cl) { fake(as: :class) { Server::HTTP } }
8
+ let(:tcp_cl) { fake(as: :class) { Server::TCP } }
9
+ let(:udp_cl) { fake(as: :class) { Server::UDP } }
10
+ let(:classes) { { http: http_cl, tcp: tcp_cl, udp: udp_cl } }
11
+
12
+ before do
13
+ http_addr = Addr['0.0.0.0', 1234, :TCP]
14
+ tcp_addr = Addr['0.0.0.0', 3456, :TCP]
15
+ udp_addr = Addr['0.0.0.0', 5678, :UDP]
16
+ stub(http_cl).new(addr: http_addr) { http }
17
+ stub(tcp_cl).new(addr: tcp_addr) { tcp }
18
+ stub(udp_cl).new(addr: udp_addr) { udp }
19
+ end
20
+
21
+ describe '.new' do
22
+ it 'hooks to the given database' do
23
+ args = %w[--host 0.0.0.0 --db sqlite::memory:]
24
+ db = fake { Sequel::SQLite::Database }
25
+ orm = fake :sequel, as: :class
26
+ stub(orm).connect('sqlite::memory:') { db }
27
+ repos = fake :repos, as: :class
28
+ ServerRunner.new args, classes: classes, orm: orm, repos: repos
29
+ repos.must_have_received :db=, [db]
30
+ end
31
+ end
32
+
33
+ describe '#join' do
34
+ it 'joins all the created servers' do
35
+ args = %w[--host 0.0.0.0 --http 1234]
36
+ ServerRunner.new(args, classes: classes).join
37
+ http.must_have_received :join, []
38
+ tcp.wont_have_received :join, []
39
+ udp.wont_have_received :join, []
40
+ end
41
+ end
42
+
43
+ describe '#start' do
44
+ it 'starts the servers based on the given command-line parameters' do
45
+ args = %w[--host 0.0.0.0 --http 1234 --tcp 3456 --udp 5678]
46
+ ServerRunner.new(args, classes: classes).start
47
+ http.must_have_received :start, []
48
+ tcp.must_have_received :start, []
49
+ udp.must_have_received :start, []
50
+ end
51
+
52
+ it 'starts only the servers for which the port was given' do
53
+ args = %w[--host 0.0.0.0 --http 1234]
54
+ ServerRunner.new(args, classes: classes).start
55
+ http.must_have_received :start, []
56
+ tcp.wont_have_received :start, []
57
+ udp.wont_have_received :start, []
58
+ end
59
+
60
+ it 'returns self' do
61
+ sr = ServerRunner.new [], classes: classes
62
+ sr.start.must_equal sr
63
+ end
64
+ end
65
+ end end
@@ -0,0 +1,23 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe TaskDispatcher do
4
+ describe '#dispatch' do
5
+ it 'dispatches tasks to free clients and marks them as busy' do
6
+ addr = fake :addr
7
+ client = fake :client, addr: addr, uuid: UUID['16B client UUID']
8
+ project = fake :project, uuid: UUID['16B project UUID']
9
+ task = fake :task, data: 'data', uuid: UUID['16B task UUID']
10
+ repos = fake :repos, as: :class, projects: [project]
11
+ stub(repos).next_task_for(project) { task }
12
+ stub(repos).free_clients_for(project) { [client] }
13
+ net_dispatcher = fake :net_dispatcher
14
+
15
+ TaskDispatcher.new(net_dispatcher: net_dispatcher, repos: repos).dispatch
16
+
17
+ net_dispatcher.must_have_received :dispatch, [addr,
18
+ "DATA#{"\0" * 12}16B client UUID16B project UUID16B task UUIDdata"]
19
+ client.must_have_received :busy=, [true]
20
+ repos.must_have_received :<<, [client]
21
+ end
22
+ end
23
+ end end
@@ -0,0 +1,9 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe Task do
4
+ describe '#done' do
5
+ it 'defaults to false' do
6
+ Task.new(data: 'data', project: fake(:project)).done.must_equal false
7
+ end
8
+ end
9
+ end end
@@ -0,0 +1,101 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe '.UUIDObject' do
4
+ describe '.from_h' do
5
+ it 'deserialises the object from a Hash' do
6
+ Trivial = Kamerling.UUIDObject :question
7
+ Trivial.from_h(question: :answer).question.must_equal :answer
8
+ end
9
+
10
+ it 'deserialises addr' do
11
+ Netable = Kamerling.UUIDObject :addr
12
+ Netable.from_h(host: '127.0.0.1', port: 1981, prot: :TCP).addr
13
+ .must_equal Addr['127.0.0.1', 1981, :TCP]
14
+ end
15
+
16
+ it 'deserialises {client,project,task}_uuid' do
17
+ client = fake :client, uuid: UUID.new
18
+ project = fake :project, uuid: UUID.new
19
+ task = fake :task, uuid: UUID.new
20
+ Complete = Kamerling.UUIDObject :client, :project, :task
21
+ repos = {
22
+ Client => { client.uuid => client },
23
+ Project => { project.uuid => project },
24
+ Task => { task.uuid => task },
25
+ }
26
+ hash = { client_uuid: client.uuid, project_uuid: project.uuid,
27
+ task_uuid: task.uuid, uuid: UUID.new }
28
+ complete = Complete.from_h hash, repos: repos
29
+ complete.client.must_equal client
30
+ complete.project.must_equal project
31
+ complete.task.must_equal task
32
+ end
33
+ end
34
+
35
+ describe '.new' do
36
+ it 'creates a class with an UUID property defaulting to a random UUID' do
37
+ AttrLess = Kamerling.UUIDObject
38
+ AttrLess.new.uuid.must_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/)
39
+ AttrLess.new.uuid.wont_equal AttrLess.new.uuid
40
+ end
41
+
42
+ it 'allows setting custom properties and raises when they lack defaults' do
43
+ FooFul = Kamerling.UUIDObject :foo
44
+ FooFul.new(foo: 'bar').foo.must_equal 'bar'
45
+ -> { FooFul.new }.must_raise RuntimeError
46
+ end
47
+
48
+ it 'allows setting properties’ default procs' do
49
+ ProcFul = Kamerling.UUIDObject rand: -> { rand }
50
+ ProcFul.new.rand.wont_equal ProcFul.new.rand
51
+ end
52
+
53
+ it 'allows setting properties’ default values' do
54
+ ValFul = Kamerling.UUIDObject bar: :baz
55
+ ValFul.new.bar.must_equal :baz
56
+ end
57
+ end
58
+
59
+ describe '#==' do
60
+ it 'reports UUID-based euqality' do
61
+ Actor = Kamerling.UUIDObject :name
62
+ Actor.new(name: :laurel).wont_equal Actor.new name: :laurel
63
+ uuid = UUID.new
64
+ Actor.new(name: :laurel, uuid: uuid)
65
+ .must_equal Actor.new name: :hardy, uuid: uuid
66
+ end
67
+ end
68
+
69
+ describe '#to_h' do
70
+ it 'serialises the object to a Hash' do
71
+ Hashble = Kamerling.UUIDObject :param
72
+ Hashble.new(param: :val).to_h.must_equal param: :val, uuid: anything
73
+ end
74
+
75
+ it 'serialises addr' do
76
+ Addrble = Kamerling.UUIDObject :addr
77
+ addrble = Addrble.new addr: Addr['127.0.0.1', 1981, :TCP]
78
+ addrble.to_h.must_equal host: '127.0.0.1', port: 1981, prot: 'TCP',
79
+ uuid: anything
80
+ end
81
+
82
+ it 'serialises client' do
83
+ Clintable = Kamerling.UUIDObject :client
84
+ clintable = Clintable.new client: client = Client.new(addr: fake(:addr))
85
+ clintable.to_h.must_equal client_uuid: client.uuid, uuid: anything
86
+ end
87
+
88
+ it 'serialises project' do
89
+ Projable = Kamerling.UUIDObject :project
90
+ projable = Projable.new project: project = Project.new(name: 'name')
91
+ projable.to_h.must_equal project_uuid: project.uuid, uuid: anything
92
+ end
93
+
94
+ it 'serialises task' do
95
+ project = fake :project
96
+ Tskble = Kamerling.UUIDObject :task
97
+ tskble = Tskble.new task: task = Task.new(data: 'data', project: project)
98
+ tskble.to_h.must_equal task_uuid: task.uuid, uuid: anything
99
+ end
100
+ end
101
+ end end
@@ -0,0 +1,24 @@
1
+ require_relative '../spec_helper'
2
+
3
+ module Kamerling describe UUID do
4
+ describe '.[]' do
5
+ it 'creates a normal UUID representation from binary data' do
6
+ uuid = '31364220-7072-6f6a-6563-742055554944'
7
+ UUID['16B project UUID'].must_equal uuid
8
+ end
9
+ end
10
+
11
+ describe '.bin' do
12
+ it 'returns the binary representation of an UUID' do
13
+ UUID.bin('31364220-7072-6f6a-6563-742055554944')
14
+ .must_equal '16B project UUID'
15
+ end
16
+ end
17
+
18
+ describe '.new' do
19
+ it 'creates a new random UUID' do
20
+ UUID.new.must_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/)
21
+ UUID.new.wont_equal UUID.new
22
+ end
23
+ end
24
+ end end
@@ -0,0 +1,26 @@
1
+ require 'bundler/setup'
2
+ require 'minitest/autorun'
3
+ require 'minitest/focus'
4
+ require 'minitest/pride'
5
+ require 'bogus/minitest/spec'
6
+ require 'net/http'
7
+ require 'rack/test'
8
+ require 'kamerling'
9
+
10
+ Bogus.configure { |config| config.search_modules << Kamerling }
11
+
12
+ module MiniTest::Spec::DSL
13
+ def fakes *args
14
+ args.map { |arg| fake arg }
15
+ end
16
+ end
17
+
18
+ class MiniTest::Spec
19
+ include Rack::Test::Methods
20
+ end
21
+
22
+ Thread.abort_on_exception = true
23
+
24
+ def run_all_threads
25
+ sleep 0.001
26
+ end