simultaneous 0.2.0
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.
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README +0 -0
- data/Rakefile +152 -0
- data/bin/simultaneous-console +139 -0
- data/bin/simultaneous-server +60 -0
- data/lib/simultaneous/broadcast_message.rb +63 -0
- data/lib/simultaneous/client.rb +111 -0
- data/lib/simultaneous/command/client_event.rb +24 -0
- data/lib/simultaneous/command/fire.rb +155 -0
- data/lib/simultaneous/command/kill.rb +19 -0
- data/lib/simultaneous/command/set_pid.rb +22 -0
- data/lib/simultaneous/command/task_complete.rb +18 -0
- data/lib/simultaneous/command.rb +75 -0
- data/lib/simultaneous/connection.rb +81 -0
- data/lib/simultaneous/rack.rb +83 -0
- data/lib/simultaneous/server.rb +74 -0
- data/lib/simultaneous/task.rb +37 -0
- data/lib/simultaneous/task_client.rb +37 -0
- data/lib/simultaneous/task_description.rb +37 -0
- data/lib/simultaneous.rb +265 -0
- data/simultaneous.gemspec +96 -0
- data/test/helper.rb +14 -0
- data/test/tasks/example.rb +22 -0
- data/test/test_client.rb +24 -0
- data/test/test_command.rb +72 -0
- data/test/test_connection.rb +91 -0
- data/test/test_faf.rb +43 -0
- data/test/test_message.rb +81 -0
- data/test/test_server.rb +242 -0
- data/test/test_task.rb +21 -0
- metadata +145 -0
data/test/test_server.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe Simultaneous::Server do
|
4
|
+
before do
|
5
|
+
Simultaneous.reset!
|
6
|
+
end
|
7
|
+
|
8
|
+
after do
|
9
|
+
FileUtils.rm(SOCKET) if File.exist?(SOCKET)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "task messages" do
|
13
|
+
it "should generate events in clients on the right domain" do
|
14
|
+
client1 = client2 = client3 = nil
|
15
|
+
result1 = result2 = result3 = nil
|
16
|
+
result4 = result5 = result6 = nil
|
17
|
+
|
18
|
+
EM.run {
|
19
|
+
Simultaneous::Server.start(SOCKET)
|
20
|
+
|
21
|
+
client1 = Simultaneous::Client.new("domain1", SOCKET)
|
22
|
+
client2 = Simultaneous::Client.new("domain1", SOCKET)
|
23
|
+
client3 = Simultaneous::Client.new("domain2", SOCKET)
|
24
|
+
|
25
|
+
command = Simultaneous::Command::ClientEvent.new("domain1", "a", "data")
|
26
|
+
|
27
|
+
client1.on_event(:a) { |event| result1 = [:a, event.data] }
|
28
|
+
client2.on_event(:a) { |event| result2 = [:a, event.data] }
|
29
|
+
client3.on_event(:a) { |event| result3 = [:a, event.data] }
|
30
|
+
client1.on_event(:b) { |event| result4 = [:b, event.data] }
|
31
|
+
client2.on_event(:b) { |event| result5 = [:b, event.data] }
|
32
|
+
client3.on_event(:b) { |event| result6 = [:b, event.data] }
|
33
|
+
|
34
|
+
$receive_count = 0
|
35
|
+
|
36
|
+
$test = proc {
|
37
|
+
result1.must_equal [:a, "data"]
|
38
|
+
result2.must_equal [:a, "data"]
|
39
|
+
result3.must_be_nil
|
40
|
+
result4.must_be_nil
|
41
|
+
result5.must_be_nil
|
42
|
+
result6.must_be_nil
|
43
|
+
EM.stop
|
44
|
+
}
|
45
|
+
|
46
|
+
def client1.notify!
|
47
|
+
super
|
48
|
+
$receive_count += 1
|
49
|
+
if $receive_count == 3
|
50
|
+
$test.call
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def client2.notify!
|
55
|
+
super
|
56
|
+
$receive_count += 1
|
57
|
+
if $receive_count == 3
|
58
|
+
$test.call
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def client3.notify!
|
63
|
+
super
|
64
|
+
$receive_count += 1
|
65
|
+
if $receive_count == 3
|
66
|
+
$test.call
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Thread.new {
|
71
|
+
Simultaneous::Server.run(command)
|
72
|
+
}.join
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "Task" do
|
78
|
+
it "should make it intact from client to process" do
|
79
|
+
Simultaneous.domain = "example.org"
|
80
|
+
Simultaneous.connection = SOCKET
|
81
|
+
|
82
|
+
default_params = { :param1 => "param1" }
|
83
|
+
env = { "ENV_PARAM" => "envparam" }
|
84
|
+
args = {:param2 => "param2"}
|
85
|
+
niceness = 10
|
86
|
+
task_uid = 9999
|
87
|
+
options = {
|
88
|
+
:nice => niceness
|
89
|
+
}
|
90
|
+
task = Simultaneous.add_task(:publish, "/path/to/binary", options, default_params, env)
|
91
|
+
command = Simultaneous::Command::Fire.new(task, args)
|
92
|
+
dump = command.dump
|
93
|
+
mock(command).dump { dump }
|
94
|
+
mock(Simultaneous::Command::Fire).new(task, args) { command }
|
95
|
+
mock(Simultaneous::Command).load(is_a(String)) { command }
|
96
|
+
mock(command).valid? { true }
|
97
|
+
mock(command).task_uid.twice { task_uid }
|
98
|
+
|
99
|
+
EM.run do
|
100
|
+
Simultaneous::Server.start(SOCKET)
|
101
|
+
|
102
|
+
mock(Process).detach(9999)
|
103
|
+
|
104
|
+
mock(command).fork do |block|
|
105
|
+
mock(command).daemonize(%[/path/to/binary --param1="param1" --param2="param2"], is_a(String))
|
106
|
+
mock(Process).setpriority(Process::PRIO_PROCESS, 0, niceness)
|
107
|
+
mock(Process::UID).change_privilege(task_uid)
|
108
|
+
mock(File).umask(0022)
|
109
|
+
mock(command).exec(%[/path/to/binary --param1="param1" --param2="param2"])
|
110
|
+
block.call
|
111
|
+
|
112
|
+
ENV["ENV_PARAM"].must_equal "envparam"
|
113
|
+
ENV[Simultaneous::ENV_DOMAIN].must_equal "example.org"
|
114
|
+
ENV[Simultaneous::ENV_TASK_NAME].must_equal "publish"
|
115
|
+
ENV[Simultaneous::ENV_CONNECTION].must_equal SOCKET
|
116
|
+
EM.stop
|
117
|
+
9999
|
118
|
+
end
|
119
|
+
|
120
|
+
Thread.new do
|
121
|
+
Simultaneous.fire(:publish, args)
|
122
|
+
end.join
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should report back to the server to set the task PID" do
|
127
|
+
pids = {}
|
128
|
+
pid = 99999
|
129
|
+
Simultaneous.domain = "example.com"
|
130
|
+
Simultaneous.connection = SOCKET
|
131
|
+
|
132
|
+
|
133
|
+
EM.run do
|
134
|
+
Simultaneous::Server.start(SOCKET)
|
135
|
+
|
136
|
+
ENV[Simultaneous::ENV_DOMAIN] = "example.com"
|
137
|
+
ENV[Simultaneous::ENV_TASK_NAME] = "publish"
|
138
|
+
ENV[Simultaneous::ENV_CONNECTION] = SOCKET
|
139
|
+
|
140
|
+
mock(Simultaneous::Task).pid { pid }
|
141
|
+
mock(Simultaneous::Server).pids { pids }
|
142
|
+
mock(pids).[]=("example.com/publish", pid) { EM.stop }
|
143
|
+
|
144
|
+
Thread.new do
|
145
|
+
class SimultaneousTask
|
146
|
+
include Simultaneous::Task
|
147
|
+
end
|
148
|
+
end.join
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it "should be able to trigger messages on the client" do
|
153
|
+
Simultaneous.domain = "example2.com"
|
154
|
+
Simultaneous.connection = SOCKET
|
155
|
+
|
156
|
+
|
157
|
+
EM.run do
|
158
|
+
Simultaneous::Server.start(SOCKET)
|
159
|
+
|
160
|
+
ENV[Simultaneous::ENV_DOMAIN] = "example2.com"
|
161
|
+
ENV[Simultaneous::ENV_TASK_NAME] = "publish"
|
162
|
+
ENV[Simultaneous::ENV_CONNECTION] = SOCKET
|
163
|
+
|
164
|
+
c = Simultaneous::TaskClient.new("example2.com", SOCKET)
|
165
|
+
mock(Simultaneous::TaskClient).new { c }
|
166
|
+
mock(c).run(is_a(Simultaneous::Command::SetPid))
|
167
|
+
proxy(c).run(is_a(Simultaneous::Command::ClientEvent))
|
168
|
+
client = Simultaneous::Client.new("example2.com", SOCKET)
|
169
|
+
|
170
|
+
client.on_event("publish_status") { |event|
|
171
|
+
event.data.must_equal "completed"
|
172
|
+
EM.stop
|
173
|
+
}
|
174
|
+
|
175
|
+
Thread.new do
|
176
|
+
class SimultaneousTask
|
177
|
+
include Simultaneous::Task
|
178
|
+
def run
|
179
|
+
simultaneous_event("publish_status", "completed")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
SimultaneousTask.new.run
|
183
|
+
end.join
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should be able to kill task processes" do
|
189
|
+
pid = 99999
|
190
|
+
pids = {"example3.com/publish" => pid}
|
191
|
+
Simultaneous.domain = "example3.com"
|
192
|
+
Simultaneous.connection = SOCKET
|
193
|
+
|
194
|
+
|
195
|
+
EM.run do
|
196
|
+
Simultaneous::Server.start(SOCKET)
|
197
|
+
|
198
|
+
ENV[Simultaneous::ENV_DOMAIN] = "example3.com"
|
199
|
+
ENV[Simultaneous::ENV_TASK_NAME] = "publish"
|
200
|
+
ENV[Simultaneous::ENV_CONNECTION] = SOCKET
|
201
|
+
|
202
|
+
c = Simultaneous::TaskClient.new("example3.com", SOCKET)
|
203
|
+
mock(Simultaneous::TaskClient).new { c }
|
204
|
+
mock(c).run(is_a(Simultaneous::Command::SetPid))
|
205
|
+
proxy(c).run(is_a(Simultaneous::Command::Kill))
|
206
|
+
|
207
|
+
mock(Simultaneous::Task).pid { pid }
|
208
|
+
mock(Simultaneous::Server).pids { pids }
|
209
|
+
|
210
|
+
Thread.new do
|
211
|
+
class SimultaneousTask
|
212
|
+
include Simultaneous::Task
|
213
|
+
end
|
214
|
+
end.join
|
215
|
+
|
216
|
+
Thread.new do
|
217
|
+
mock(Process).kill("TERM", pid) { EM.stop }
|
218
|
+
Simultaneous.kill(:publish)
|
219
|
+
end.join
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should divert STDOUT and STDERR to file and inform the server when finished" do
|
224
|
+
Simultaneous.domain = "example4.com"
|
225
|
+
Simultaneous.connection = SOCKET
|
226
|
+
logfile = "/tmp/log-#{$$}/#{$$}-example.log"
|
227
|
+
FileUtils.rm_f(logfile) if File.exists?(logfile)
|
228
|
+
EM.run do
|
229
|
+
Simultaneous::Server.start(SOCKET)
|
230
|
+
task = Simultaneous.add_task(:example, File.expand_path("../tasks/example.rb", __FILE__), {:logfile => logfile})
|
231
|
+
proxy(Simultaneous::Server).run(is_a(Simultaneous::Command::Fire))
|
232
|
+
mock(Simultaneous::Server).run(is_a(Simultaneous::Command::SetPid))
|
233
|
+
mock(Simultaneous::Server).run(is_a(Simultaneous::Command::ClientEvent))
|
234
|
+
mock(Simultaneous::Server).run(is_a(Simultaneous::Command::TaskComplete)) { EM.stop }
|
235
|
+
Simultaneous.fire(:example, { "param" => "value" })
|
236
|
+
end
|
237
|
+
assert(File.exist?(logfile), "Task should have output to #{logfile}")
|
238
|
+
File.read(logfile).must_equal %(--param=value\n)
|
239
|
+
FileUtils.rm_rf(File.dirname(logfile)) if File.exists?(logfile)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/test/test_task.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
describe Simultaneous::TaskDescription do
|
4
|
+
it "should generate the right default logfile location" do
|
5
|
+
mock(Dir).pwd { "/application/current" }
|
6
|
+
task = Simultaneous::TaskDescription.new(:fish, "/path/to/fish")
|
7
|
+
task.logfile.must_equal "/application/current/log/fish-task.log"
|
8
|
+
end
|
9
|
+
it "should generate the right default logfile location" do
|
10
|
+
task = Simultaneous::TaskDescription.new(:fish, "/path/to/fish", {:logfile => "/var/log/fish.log"})
|
11
|
+
task.logfile.must_equal "/var/log/fish.log"
|
12
|
+
end
|
13
|
+
it "should generate the right default logfile location" do
|
14
|
+
task = Simultaneous::TaskDescription.new(:fish, "/path/to/fish", {:log => "/var/log/fish.log"})
|
15
|
+
task.logfile.must_equal "/var/log/fish.log"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the right pwd" do
|
19
|
+
flunk
|
20
|
+
end
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: simultaneous
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Garry Hill
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-03 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &70278663630360 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0.beta.4
|
22
|
+
- - <
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: '2.0'
|
25
|
+
type: :runtime
|
26
|
+
prerelease: false
|
27
|
+
version_requirements: *70278663630360
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rack
|
30
|
+
requirement: &70278663629460 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ! '>='
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '1.0'
|
36
|
+
- - <
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '2.0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: *70278663629460
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rack-async
|
44
|
+
requirement: &70278663628360 !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 0.0.1
|
50
|
+
- - <
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '2.0'
|
53
|
+
type: :runtime
|
54
|
+
prerelease: false
|
55
|
+
version_requirements: *70278663628360
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: rr
|
58
|
+
requirement: &70278663626460 !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.0.4
|
64
|
+
type: :development
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: *70278663626460
|
67
|
+
description: Simultaneous is designed for the very specific use case of a small set
|
68
|
+
of users collaborating on editing a single website. Because of that it is optimised
|
69
|
+
for infrequent invocation of very long running publishing tasks and provides an
|
70
|
+
event based messaging system that allows launched tasks to communicate back to the
|
71
|
+
CMS web-server and for that server to then fire off update messages through HTML5
|
72
|
+
Server-Sent Events.
|
73
|
+
email: garry@magnetised.info
|
74
|
+
executables:
|
75
|
+
- simultaneous-server
|
76
|
+
- simultaneous-console
|
77
|
+
extensions: []
|
78
|
+
extra_rdoc_files:
|
79
|
+
- README
|
80
|
+
- LICENSE
|
81
|
+
files:
|
82
|
+
- Gemfile
|
83
|
+
- LICENSE
|
84
|
+
- README
|
85
|
+
- Rakefile
|
86
|
+
- bin/simultaneous-console
|
87
|
+
- bin/simultaneous-server
|
88
|
+
- lib/simultaneous.rb
|
89
|
+
- lib/simultaneous/broadcast_message.rb
|
90
|
+
- lib/simultaneous/client.rb
|
91
|
+
- lib/simultaneous/command.rb
|
92
|
+
- lib/simultaneous/command/client_event.rb
|
93
|
+
- lib/simultaneous/command/fire.rb
|
94
|
+
- lib/simultaneous/command/kill.rb
|
95
|
+
- lib/simultaneous/command/set_pid.rb
|
96
|
+
- lib/simultaneous/command/task_complete.rb
|
97
|
+
- lib/simultaneous/connection.rb
|
98
|
+
- lib/simultaneous/rack.rb
|
99
|
+
- lib/simultaneous/server.rb
|
100
|
+
- lib/simultaneous/task.rb
|
101
|
+
- lib/simultaneous/task_client.rb
|
102
|
+
- lib/simultaneous/task_description.rb
|
103
|
+
- simultaneous.gemspec
|
104
|
+
- test/helper.rb
|
105
|
+
- test/tasks/example.rb
|
106
|
+
- test/test_client.rb
|
107
|
+
- test/test_command.rb
|
108
|
+
- test/test_connection.rb
|
109
|
+
- test/test_faf.rb
|
110
|
+
- test/test_message.rb
|
111
|
+
- test/test_server.rb
|
112
|
+
- test/test_task.rb
|
113
|
+
homepage: http://spontaneouscms.org
|
114
|
+
licenses: []
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options:
|
117
|
+
- --charset=UTF-8
|
118
|
+
require_paths:
|
119
|
+
- lib
|
120
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubyforge_project: simultaneous
|
134
|
+
rubygems_version: 1.8.10
|
135
|
+
signing_key:
|
136
|
+
specification_version: 2
|
137
|
+
summary: Simultaneous is the background task launcher used by Spontaneous CMS
|
138
|
+
test_files:
|
139
|
+
- test/test_client.rb
|
140
|
+
- test/test_command.rb
|
141
|
+
- test/test_connection.rb
|
142
|
+
- test/test_faf.rb
|
143
|
+
- test/test_message.rb
|
144
|
+
- test/test_server.rb
|
145
|
+
- test/test_task.rb
|