leonidas 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. data/CHANGELOG +1 -0
  2. data/Gemfile +15 -0
  3. data/Gemfile.lock +105 -0
  4. data/LICENSE +21 -0
  5. data/Manifest +63 -0
  6. data/README.md +213 -0
  7. data/Rakefile +42 -0
  8. data/assets/scripts/coffee/leonidas/client.coffee +15 -0
  9. data/assets/scripts/coffee/leonidas/commander.coffee +34 -0
  10. data/assets/scripts/coffee/leonidas/commands/command.coffee +9 -0
  11. data/assets/scripts/coffee/leonidas/commands/organizer.coffee +24 -0
  12. data/assets/scripts/coffee/leonidas/commands/processor.coffee +13 -0
  13. data/assets/scripts/coffee/leonidas/commands/stabilizer.coffee +13 -0
  14. data/assets/scripts/coffee/leonidas/commands/synchronizer.coffee +38 -0
  15. data/assets/scripts/js/lib/jquery.js +6 -0
  16. data/bin/leonidas.js +178 -0
  17. data/config/assets.rb +7 -0
  18. data/leonidas.gemspec +36 -0
  19. data/lib/leonidas.rb +14 -0
  20. data/lib/leonidas/app/app.rb +80 -0
  21. data/lib/leonidas/app/connection.rb +20 -0
  22. data/lib/leonidas/app/repository.rb +41 -0
  23. data/lib/leonidas/commands/aggregator.rb +31 -0
  24. data/lib/leonidas/commands/command.rb +24 -0
  25. data/lib/leonidas/commands/handler.rb +21 -0
  26. data/lib/leonidas/commands/processor.rb +30 -0
  27. data/lib/leonidas/dsl/configuration_expression.rb +17 -0
  28. data/lib/leonidas/memory_layer/memory_registry.rb +33 -0
  29. data/lib/leonidas/persistence_layer/persister.rb +54 -0
  30. data/lib/leonidas/persistence_layer/state_builder.rb +17 -0
  31. data/lib/leonidas/persistence_layer/state_loader.rb +22 -0
  32. data/lib/leonidas/routes/sync.rb +45 -0
  33. data/lib/leonidas/symbols.rb +17 -0
  34. data/spec/jasmine/jasmine.yml +44 -0
  35. data/spec/jasmine/runner.html +77 -0
  36. data/spec/jasmine/support/classes.coffee +16 -0
  37. data/spec/jasmine/support/helpers.coffee +22 -0
  38. data/spec/jasmine/support/mocks.coffee +19 -0
  39. data/spec/jasmine/support/objects.coffee +11 -0
  40. data/spec/jasmine/support/requirements.coffee +1 -0
  41. data/spec/jasmine/tests/client_spec.coffee +20 -0
  42. data/spec/jasmine/tests/commander_spec.coffee +69 -0
  43. data/spec/jasmine/tests/commands/command_spec.coffee +12 -0
  44. data/spec/jasmine/tests/commands/organizer_spec.coffee +70 -0
  45. data/spec/jasmine/tests/commands/processor_spec.coffee +22 -0
  46. data/spec/jasmine/tests/commands/stabilizer_spec.coffee +30 -0
  47. data/spec/jasmine/tests/commands/synchronizer_spec.coffee +72 -0
  48. data/spec/rspec/spec_helper.rb +4 -0
  49. data/spec/rspec/support/classes/app.rb +26 -0
  50. data/spec/rspec/support/classes/commands.rb +52 -0
  51. data/spec/rspec/support/classes/persistence.rb +56 -0
  52. data/spec/rspec/support/config.rb +3 -0
  53. data/spec/rspec/support/mocks.rb +15 -0
  54. data/spec/rspec/support/objects.rb +11 -0
  55. data/spec/rspec/unit/app/app_spec.rb +185 -0
  56. data/spec/rspec/unit/app/repository_spec.rb +114 -0
  57. data/spec/rspec/unit/commands/aggregator_spec.rb +103 -0
  58. data/spec/rspec/unit/commands/command.rb +17 -0
  59. data/spec/rspec/unit/commands/processor_spec.rb +30 -0
  60. data/spec/rspec/unit/dsl/configuration_expression_spec.rb +32 -0
  61. data/spec/rspec/unit/leonidas_spec.rb +26 -0
  62. data/spec/rspec/unit/memory_layer/memory_registry_spec.rb +85 -0
  63. data/spec/rspec/unit/persistence_layer/persister_spec.rb +84 -0
  64. data/spec/rspec/unit/persistence_layer/state_loader_spec.rb +29 -0
  65. metadata +166 -0
@@ -0,0 +1,56 @@
1
+ module TestClasses
2
+
3
+ class PersistentState
4
+
5
+ def self.reset
6
+ @@value = 0
7
+ end
8
+
9
+ def self.value
10
+ @@value
11
+ end
12
+
13
+ def self.value=(val)
14
+ @@value = val
15
+ end
16
+ end
17
+
18
+ class TestAppPersister
19
+ include ::Leonidas::PersistenceLayer::AppPersister
20
+
21
+ def initialize(apps=[])
22
+ @apps = apps
23
+ end
24
+
25
+ def clear_apps!
26
+ @apps = [ ]
27
+ end
28
+
29
+ def load(app_name)
30
+ @apps.select {|app| app.name == app_name}.first
31
+ end
32
+
33
+ def persist(app)
34
+ @apps << app
35
+ end
36
+
37
+ def delete(app)
38
+ @apps.delete app
39
+ end
40
+
41
+ end
42
+
43
+ class TestAppStateBuilder
44
+ include ::Leonidas::PersistenceLayer::StateBuilder
45
+
46
+ def handles?(app)
47
+ app.is_a? TestClasses::TestApp
48
+ end
49
+
50
+ def build_stable_state(app)
51
+ app.state = { value: 0 }
52
+ end
53
+
54
+ end
55
+
56
+ end
@@ -0,0 +1,3 @@
1
+ persister_class_is TestClasses::TestAppPersister
2
+
3
+ add_app_state_builder TestClasses::TestAppStateBuilder
@@ -0,0 +1,15 @@
1
+ module TestMocks
2
+
3
+ class MockApp
4
+
5
+ def initialize
6
+ @state = { value: 0 }
7
+ end
8
+
9
+ def current_state
10
+ @state
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,11 @@
1
+ module TestObjects
2
+
3
+ def build_command(connection, timestamp, name="increment", data={ increment_by: 1 })
4
+ ::Leonidas::Commands::Command.new(name, data, timestamp, connection)
5
+ end
6
+
7
+ def build_connection
8
+ ::Leonidas::App::Connection.new
9
+ end
10
+
11
+ end
@@ -0,0 +1,185 @@
1
+ describe Leonidas::App::App do
2
+ include TestObjects
3
+
4
+ subject do
5
+ TestClasses::TestApp.new
6
+ end
7
+
8
+ before :each do
9
+ TestClasses::PersistentState.reset
10
+ end
11
+
12
+ describe '#name' do
13
+
14
+ it "will return the name defined in the includer class" do
15
+ subject.name.should eq "app 1"
16
+ end
17
+
18
+ end
19
+
20
+ describe '#current_state' do
21
+
22
+ it "will return the current state of the application" do
23
+ subject.current_state.should eq({ value: 1 })
24
+ end
25
+
26
+ end
27
+
28
+ describe '#create_connection!' do
29
+
30
+ it "will return a Leonidas::App::Connection" do
31
+ subject.create_connection!.should be_a Leonidas::App::Connection
32
+ end
33
+
34
+ it "will add the new connection the the app's list of connections" do
35
+ conn = subject.create_connection!
36
+ subject.should have_connection(conn.id)
37
+ end
38
+
39
+ end
40
+
41
+ describe '#close_connection!' do
42
+
43
+ it "will remove the connection" do
44
+ conn = subject.create_connection!
45
+ subject.close_connection! conn.id
46
+ subject.should_not have_connection(conn.id)
47
+ end
48
+
49
+ end
50
+
51
+ describe '#connection' do
52
+
53
+ it "will return nil if the requested connection doesn't exist" do
54
+ subject.connection('badid').should be_nil
55
+ end
56
+
57
+ it "will retrieve the requested connection" do
58
+ conn = subject.create_connection!
59
+ subject.connection(conn.id).should eq conn
60
+ end
61
+
62
+ end
63
+
64
+ describe '#has_connection?' do
65
+
66
+ it "will return true if it has the requested connection" do
67
+ conn = subject.create_connection!
68
+ subject.should have_connection(conn.id)
69
+ end
70
+
71
+ it "will return false if it doesn't have the requested connection" do
72
+ subject.should_not have_connection("badid")
73
+ end
74
+
75
+ end
76
+
77
+ describe '#connections' do
78
+
79
+ it "will return the full list of connections" do
80
+ conn1 = subject.create_connection!
81
+ conn2 = subject.create_connection!
82
+ subject.connections.should eq [ conn1, conn2 ]
83
+ end
84
+
85
+ end
86
+
87
+ describe '#stable_timestamp' do
88
+
89
+ it "will default to 0 if there are no connections" do
90
+ subject.stable_timestamp.should eq 0
91
+ end
92
+
93
+ it "will return the current minimum timestamp between all connections" do
94
+ conn1 = subject.create_connection!
95
+ conn2 = subject.create_connection!
96
+ conn3 = subject.create_connection!
97
+ subject.stable_timestamp.should eq conn1.last_update
98
+ conn3.last_update = Time.now.to_i
99
+ subject.stable_timestamp.should eq conn2.last_update
100
+ conn3.last_update = conn1.last_update - 10
101
+ subject.stable_timestamp.should eq conn3.last_update
102
+ end
103
+
104
+ end
105
+
106
+ describe '#stabilize!' do
107
+
108
+ before :each do
109
+ conn1 = subject.create_connection!
110
+ conn2 = subject.create_connection!
111
+ @command3 = build_command(conn1, conn1.last_update+5)
112
+ conn1.add_commands! [ build_command(conn1, conn1.last_update-5), @command3 ]
113
+ conn2.add_command! build_command(conn2, conn2.last_update-5)
114
+ end
115
+
116
+ context "when the app is set to be persistent" do
117
+
118
+ it "will persist all commands which occured at or before the stable timestamp" do
119
+ subject.instance_variable_set :@persist_state, true
120
+ subject.stabilize!
121
+ TestClasses::PersistentState.value.should eq 2
122
+ end
123
+
124
+ end
125
+
126
+ it "will reduce the active commands by the stable commands" do
127
+ subject.stabilize!
128
+ subject.active_commands.should eq [ @command3 ]
129
+ end
130
+
131
+ it "will set the current state to the state when all stable commands have been run" do
132
+ subject.stabilize!
133
+ subject.current_state.should eq({ value: 2 })
134
+ end
135
+
136
+ end
137
+
138
+ describe '#process_commands!' do
139
+
140
+ before :each do
141
+ conn1 = subject.create_connection!
142
+ conn2 = subject.create_connection!
143
+ @command3 = build_command(conn1, conn1.last_update+5)
144
+ conn1.add_commands! [ build_command(conn1, conn1.last_update-5), @command3 ]
145
+ conn2.add_command! build_command(conn2, conn2.last_update-5)
146
+ end
147
+
148
+ context "when the app is set to be persistent" do
149
+
150
+ it "will persist all commands which occured at or before the stable timestamp" do
151
+ subject.instance_variable_set :@persist_state, true
152
+ subject.process_commands!
153
+ TestClasses::PersistentState.value.should eq 2
154
+ end
155
+
156
+ end
157
+
158
+ it "will reduce the active commands by the stable commands" do
159
+ subject.stabilize!
160
+ subject.active_commands.should eq [ @command3 ]
161
+ end
162
+
163
+ it "will set the current state to the state when all active commands have been run" do
164
+ subject.process_commands!
165
+ subject.current_state.should eq({ value: 3 })
166
+ end
167
+
168
+ end
169
+
170
+ describe '#active_commands' do
171
+
172
+ it "will return a list of all active commands" do
173
+ conn1 = subject.create_connection!
174
+ conn2 = subject.create_connection!
175
+ command1 = build_command(conn1, 1)
176
+ command2 = build_command(conn2, 2)
177
+ command3 = build_command(conn1, 3)
178
+ conn1.add_commands! [ command1, command3 ]
179
+ conn2.add_command! command2
180
+ subject.active_commands.should eq [ command1, command3, command2 ]
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -0,0 +1,114 @@
1
+ describe Leonidas::App::Repository do
2
+
3
+ def clear_persistence_layer
4
+ persistence_layer.class_variable_set(:@@persister, nil)
5
+ persistence_layer.class_variable_get(:@@state_loader).instance_variable_set(:@builders, [])
6
+ end
7
+
8
+ def persistence_layer
9
+ Leonidas::PersistenceLayer::Persister
10
+ end
11
+
12
+ def memory_layer
13
+ Leonidas::MemoryLayer::MemoryRegistry
14
+ end
15
+
16
+ before :each do
17
+ @app = TestClasses::TestApp.new
18
+ @persister = TestClasses::TestAppPersister.new
19
+ persistence_layer.set_app_persister! @persister
20
+ end
21
+
22
+ after :each do
23
+ memory_layer.clear_registry!
24
+ clear_persistence_layer
25
+ @persister.clear_apps!
26
+ end
27
+
28
+ describe '#find' do
29
+
30
+ it "will return the app if the app is being watched" do
31
+ subject.watch @app
32
+ subject.find("app 1").should eq @app
33
+ end
34
+
35
+ context "when the app is not being watched" do
36
+
37
+ it "will return nil if the app is not persisted" do
38
+ subject.find("app 1").should be_nil
39
+ end
40
+
41
+ it "will load the app from disk" do
42
+ subject.save @app
43
+ subject.find("app 1").should eq @app
44
+ end
45
+
46
+ it "will begin watching the app when it was persisted" do
47
+ subject.save @app
48
+ subject.find("app 1")
49
+ memory_layer.should have_app "app 1"
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ describe '#watch' do
57
+
58
+ it "will register the app in the memory layer" do
59
+ subject.watch @app
60
+ memory_layer.should have_app 'app 1'
61
+ end
62
+
63
+ end
64
+
65
+ describe '#save' do
66
+
67
+ it "will persist the app" do
68
+ subject.save @app
69
+ persistence_layer.load("app 1").should eq @app
70
+ end
71
+
72
+ end
73
+
74
+ describe '#archive' do
75
+
76
+ it "will close the app in the memory layer" do
77
+ subject.watch @app
78
+ subject.archive @app
79
+ memory_layer.should_not have_app 'app 1'
80
+ end
81
+
82
+ it "will persist the app" do
83
+ subject.watch @app
84
+ subject.archive @app
85
+ persistence_layer.load("app 1").should eq @app
86
+ end
87
+
88
+ end
89
+
90
+ describe '#delete' do
91
+
92
+ it "will close an app in memory" do
93
+ subject.watch @app
94
+ subject.delete @app
95
+ memory_layer.should_not have_app "app 1"
96
+ end
97
+
98
+ it "will remove an app from the persistence layer" do
99
+ subject.save @app
100
+ subject.delete @app
101
+ persistence_layer.load("app 1").should be_nil
102
+ end
103
+
104
+ end
105
+
106
+ describe 'AppRepository mixin' do
107
+
108
+ it "Leonidas::App::AppRepository can be used as a mixin to provide #app_repository" do
109
+ TestClasses::TestRepositoryContainer.new.app_repository.should be_a Leonidas::App::Repository
110
+ end
111
+
112
+ end
113
+
114
+ end
@@ -0,0 +1,103 @@
1
+ describe Leonidas::Commands::Aggregator do
2
+ include TestObjects
3
+
4
+ subject do
5
+ TestClasses::TestAggregator.new
6
+ end
7
+
8
+ describe '#add_command!' do
9
+
10
+ it "will reject arguments not of type Leonidas::Commands::Command" do
11
+ expect { subject.add_command!({ name: "increment" }) }.to raise_error(TypeError, "Argument must be a Leonidas::Commands::Command")
12
+ end
13
+
14
+ it "will add a command to the list of commands" do
15
+ command = build_command(build_connection, 1)
16
+ subject.add_command! command
17
+ subject.commands_since(0).should eq [ command ]
18
+ end
19
+
20
+ end
21
+
22
+ describe '#add_commands!' do
23
+
24
+ it "will add each in the list of commands to the list of active commands" do
25
+ command1 = build_command(build_connection, 1)
26
+ command2 = build_command(build_connection, 2)
27
+ subject.add_commands! [ command1, command2 ]
28
+ subject.commands_since(0).should eq [ command1, command2 ]
29
+ end
30
+
31
+ end
32
+
33
+ describe '#commands_through' do
34
+
35
+ before :each do
36
+ @command1 = build_command(build_connection, 1)
37
+ @command2 = build_command(build_connection, 2)
38
+ @command3 = build_command(build_connection, 3)
39
+ @command4 = build_command(build_connection, 4)
40
+ subject.add_commands! [ @command1, @command2, @command3, @command4 ]
41
+ end
42
+
43
+ it "will return a list of active commands before the given timestamp" do
44
+ subject.commands_through(3).should include @command1
45
+ subject.commands_through(3).should include @command2
46
+ subject.commands_through(3).should_not include @command4
47
+ end
48
+
49
+ it "will include active commands at the given timestamp" do
50
+ subject.commands_through(3).should include @command3
51
+ end
52
+
53
+ end
54
+
55
+ describe '#commands_since' do
56
+
57
+ before :each do
58
+ @command1 = build_command(build_connection, 1)
59
+ @command2 = build_command(build_connection, 2)
60
+ @command3 = build_command(build_connection, 3)
61
+ @command4 = build_command(build_connection, 4)
62
+ subject.add_commands! [ @command1, @command2, @command3, @command4 ]
63
+ end
64
+
65
+ it "will return a list of active commands after the given timestamp" do
66
+ subject.commands_since(2).should include @command3
67
+ subject.commands_since(2).should include @command4
68
+ subject.commands_since(2).should_not include @command1
69
+ end
70
+
71
+ it "will exclude commands at the given timestamp" do
72
+ subject.commands_since(2).should_not include @command2
73
+ end
74
+
75
+ end
76
+
77
+ describe '#deactivate_commands!' do
78
+
79
+ before :each do
80
+ @command1 = build_command(build_connection, 1)
81
+ @command2 = build_command(build_connection, 2)
82
+ @command3 = build_command(build_connection, 3)
83
+ subject.add_commands! [ @command1, @command2, @command3 ]
84
+ end
85
+
86
+ it "will add the requested commands to the inactive commands" do
87
+ subject.deactivate_commands! [ @command1, @command2 ]
88
+ subject.instance_variable_get(:@inactive_commands).should eq [ @command1, @command2 ]
89
+ end
90
+
91
+ it "will not add any command which isn't a current member of the active commands" do
92
+ subject.deactivate_commands! [ build_command(build_connection, 4) ]
93
+ subject.instance_variable_get(:@inactive_commands).should be_empty
94
+ end
95
+
96
+ it "will remove the requested commands from the active commands" do
97
+ subject.deactivate_commands! [ @command1, @command2 ]
98
+ subject.instance_variable_get(:@active_commands).should eq [ @command3 ]
99
+ end
100
+
101
+ end
102
+
103
+ end