shooting_star 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/History.txt +42 -0
  2. data/Manifest.txt +38 -0
  3. data/README.txt +80 -0
  4. data/Rakefile +54 -0
  5. data/bin/shooting_star +70 -0
  6. data/ext/asteroid.c +262 -0
  7. data/ext/asteroid.h +4 -0
  8. data/ext/extconf.rb +9 -0
  9. data/lib/form_encoder.rb +23 -0
  10. data/lib/shooting_star/channel.rb +41 -0
  11. data/lib/shooting_star/config.rb +19 -0
  12. data/lib/shooting_star/server.rb +219 -0
  13. data/lib/shooting_star/shooter.rb +62 -0
  14. data/lib/shooting_star.rb +142 -0
  15. data/test/ext/asteroid_test.rb +60 -0
  16. data/test/lib/shooting_star_test.rb +55 -0
  17. data/test/lib/test_c10k_problem.c +47 -0
  18. data/test/test_helper.rb +33 -0
  19. data/test/test_shooting_star.rb +64 -0
  20. data/vendor/plugins/meteor_strike/README +50 -0
  21. data/vendor/plugins/meteor_strike/Rakefile +22 -0
  22. data/vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb +39 -0
  23. data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +25 -0
  24. data/vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb +17 -0
  25. data/vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb +13 -0
  26. data/vendor/plugins/meteor_strike/generators/meteor/templates/model.rb +15 -0
  27. data/vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb +11 -0
  28. data/vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml +13 -0
  29. data/vendor/plugins/meteor_strike/init.rb +3 -0
  30. data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +61 -0
  31. data/vendor/plugins/meteor_strike/test/meteor_strike_test.rb +15 -0
  32. metadata +100 -0
@@ -0,0 +1,219 @@
1
+ require 'json'
2
+ require 'cgi'
3
+ require 'md5'
4
+ require 'set'
5
+ require 'form_encoder'
6
+
7
+ module ShootingStar
8
+ # The module which will be included by servant who was born in the Asteroid.
9
+ # This idea is from EventMachine.
10
+ module Server
11
+ attr_reader :signature
12
+ @@servers = {}
13
+ @@uids = {}
14
+ @@tags = {}
15
+ @@executings = {}
16
+
17
+ # initialize servant waked up.
18
+ def post_init
19
+ @execution = ''
20
+ @data = ''
21
+ end
22
+
23
+ # receive the data sent from client.
24
+ def receive_data(data)
25
+ @data += data
26
+ response if @data[-4..-1] == "\r\n\r\n"
27
+ end
28
+
29
+ # detect disconnection from the client and clean it up.
30
+ def unbind
31
+ @unbound = true
32
+ if channel = Channel[@channel]
33
+ channel.leave(self)
34
+ notify(:event => :leave, :uid => @uid, :tag => @tag)
35
+ Channel.cleanup(@channel)
36
+ end
37
+ @@servers.delete(@signature)
38
+ @@uids.delete(@signature)
39
+ @@tags.delete(@signature)
40
+ @@executings.delete(@signature)
41
+ log "Disconnected: #{@uid}"
42
+ end
43
+
44
+ # respond to an execution command. it'll be buffered.
45
+ def respond(id, params)
46
+ @executing = @@executings[@signature] ||= Hash.new
47
+ if params[:tag] && !params[:tag].empty? && !@tag.empty?
48
+ return false if (params[:tag] & @tag).empty?
49
+ end
50
+ @executing[id] = params
51
+ @waiting
52
+ end
53
+
54
+ # perform buffered executions.
55
+ def commit
56
+ return false if @unbound
57
+ @executing.each{|id, params| execute(id, params)}
58
+ return false if @execution.empty?
59
+ send_data "HTTP/1.1 200 OK\nContent-Type: text/javascript\n\n"
60
+ send_data @execution
61
+ @waiting = nil
62
+ @execution = ''
63
+ @executing = Hash.new
64
+ @@executings.delete(@signature)
65
+ write_and_close
66
+ true
67
+ end
68
+
69
+ # noticed execution and remove the command from execution buffer.
70
+ def executed(id)
71
+ @executing = @@executings[@signature] ||= Hash.new
72
+ @executing.delete(id)
73
+ end
74
+
75
+ # update current status of servant.
76
+ def update(uid, tag)
77
+ if @uid != uid || @tag != tag
78
+ notify(:event => :leave, :uid => @uid, :tag => @tag)
79
+ @@uids[@signature] = @uid = uid
80
+ @@tags[@signature] = @tag = tag
81
+ notify(:event => :enter, :uid => @uid, :tag => @tag)
82
+ end
83
+ log "Update: #{@uid}:#{@tag.join(',')}"
84
+ end
85
+
86
+ def uid; @@uids[@signature] end
87
+ def tag; @@tags[@signature] end
88
+
89
+ # an accessor which maps signatures to servers.
90
+ def self.[](signature)
91
+ @@servers[signature]
92
+ end
93
+
94
+ private
95
+ def log(*arg, &block) ShootingStar::log(*arg, &block) end
96
+
97
+ # broadcast an event to clients.
98
+ def notify(params = {})
99
+ return unless Channel[@channel]
100
+ event_id = ShootingStar::timestamp
101
+ log "Event(#{event_id}): #{@channel}:#{params.inspect}"
102
+ Channel[@channel].transmit("event-#{event_id}", params)
103
+ end
104
+
105
+ # wait for commands or events until they occur. if they're already in
106
+ # the execution buffer, they'll be flushed and return on the spot.
107
+ def wait_for
108
+ log "Wait for: #{@channel}:#{@uid}:#{@tag.join(',')}"
109
+ if Channel[@channel].join(self)
110
+ log "Flushed: #{@channel}:#{@uid}:#{@tag.join(',')}"
111
+ end
112
+ @waiting = true
113
+ end
114
+
115
+ # clean up channel and it'll be closed if no one's listening.
116
+ def cleanup(channel)
117
+ if Channel.cleanup(channel)
118
+ log "Channel closed: #{@channel}"
119
+ end
120
+ end
121
+
122
+ # give a response to the request or keep them waiting.
123
+ def response
124
+ headers = @data.split("\n")
125
+ head = headers.shift
126
+ method, path, protocol = head.split(/\s+/)
127
+ # recognize header
128
+ hdr = headers.inject({}) do |hash, line|
129
+ key, value = line.chop.split(/ *?: */, 2)
130
+ hash[key.downcase] = value if key
131
+ hash
132
+ end
133
+ # recognize parameter
134
+ @params = Hash.new
135
+ if @query = path.split('?', 2)[1]
136
+ if @query = @query.split('#', 2)[0]
137
+ @query.split('&').each do |item|
138
+ key, value = item.split('=', 2)
139
+ @params[key] = CGI.unescape(value) if value && value.length > 0
140
+ end
141
+ end
142
+ end
143
+ # load or create session informations
144
+ @signature ||= @params['sig']
145
+ @channel ||= path[1..-1].split('?', 2)[0]
146
+ @uid = @@uids[@signature] ||= @params['uid']
147
+ @tag = @@tags[@signature] ||=
148
+ (@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
149
+ @executing = @@executings[@signature] ||= Hash.new
150
+ @@servers[@signature] = self
151
+ # make uncacheable path
152
+ @timestamp = ShootingStar::timestamp
153
+ path += (path.index('?') ? '&' : '?') + "timestamp=#{@timestamp}"
154
+ @query = "channel=#{@channel}&sig=#{@signature}"
155
+ # prepare channel
156
+ unless Channel[@channel]
157
+ Channel.new(@channel)
158
+ log "Channel opened: #{@channel}"
159
+ end
160
+ # process verb
161
+ if method == 'GET'
162
+ make_connection(path)
163
+ notify(:event => :enter, :uid => @uid, :tag => @tag)
164
+ log "Connected: #{@uid}"
165
+ else
166
+ wait_for
167
+ end
168
+ rescue
169
+ log "ERROR: #{$!.message}\n#{@data}"
170
+ raise
171
+ ensure
172
+ @data = ''
173
+ end
174
+
175
+ # add execution line to the buffer.
176
+ def execute(id, params)
177
+ @executing[id] = params
178
+ @query += "&" +FormEncoder.encode(params) if params
179
+ @execution += <<-"EOH"
180
+ (function(){
181
+ var iframe = document.createElement('iframe');
182
+ var remove = function(){document.body.removeChild(iframe)};
183
+ iframe.onload = function(){setTimeout(remove, 0)};
184
+ iframe.src = '#{@params['execute']}/#{id}?#{@query}';
185
+ document.body.appendChild(iframe);
186
+ })();
187
+ EOH
188
+ end
189
+
190
+ # make client connect us.
191
+ def make_connection(path)
192
+ send_data "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" +
193
+ <<-"EOH"
194
+ <html><head><script type='text/javascript'
195
+ src='http://alphastars.drecom.jp/javascripts/prototype.js'
196
+ ></script>
197
+ <script type='text/javascript'>
198
+ //<![CDATA[
199
+ var connect = function()
200
+ { var request = new Ajax.Request(
201
+ #{path.to_json}, {method: 'post', evalScript: true,
202
+ onComplete: function(xhr){
203
+ setTimeout(connect,
204
+ xhr.getResponseHeader('Content-Type') ? 0 : 1000);
205
+ }});
206
+ var disconnect = function()
207
+ { request.options.onComplete = function(){};
208
+ request.transport.abort();
209
+ };
210
+ Event.observe(window, 'unload', disconnect);
211
+ };
212
+ setTimeout(connect, 0);
213
+ //]]>
214
+ </script></head><body></body></html>
215
+ EOH
216
+ write_and_close
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,62 @@
1
+ require 'shooting_star/channel'
2
+
3
+ # DRbObject
4
+ module ShootingStar
5
+ class Shooter
6
+ def shoot(channel, id, tag)
7
+ return unless Channel[channel]
8
+ log "Shot: #{channel}:#{id}:#{tag.join(',')}"
9
+ Channel[channel].transmit(id, :tag => tag)
10
+ end
11
+
12
+ def update(sig, uid, tag)
13
+ ::ShootingStar::Server[sig].update(uid, tag || [])
14
+ end
15
+
16
+ def signature; ShootingStar::timestamp end
17
+ def channels; Channel.list end
18
+ def sweep; Channel.sweep end
19
+
20
+ def count(channel, tag = nil)
21
+ servers(channel, tag).size
22
+ end
23
+
24
+ def count_with(sig, channel, tag = nil)
25
+ (signatures(channel, tag) | [sig]).size
26
+ end
27
+
28
+ def listeners(channel, tag = nil)
29
+ servers(channel, tag).map{|s| s.uid}
30
+ end
31
+
32
+ def listeners_with(uid, sig, channel, tag = nil)
33
+ servers(channel, tag).inject([uid]) do |result, server|
34
+ result << server.uid unless server.signature == sig
35
+ result
36
+ end
37
+ end
38
+
39
+ def signatures(channel, tag = nil)
40
+ servers(channel, tag).map{|s| s.signature}
41
+ end
42
+
43
+ def executed(sig, id)
44
+ ::ShootingStar::Server[sig].executed(id)
45
+ rescue
46
+ end
47
+
48
+ private
49
+ def log(*arg, &block) ShootingStar::log(*arg, &block) end
50
+
51
+ def servers(channel, tag = nil)
52
+ return [] unless Channel[channel]
53
+ result = Channel[channel].waiters.values
54
+ if tag && !tag.empty?
55
+ result = result.select do |server|
56
+ server.tag.empty? || !(server.tag & tag).empty?
57
+ end
58
+ end
59
+ result
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,142 @@
1
+ require 'rubygems'
2
+ require 'asteroid'
3
+ require 'drb/drb'
4
+ require 'yaml'
5
+ require 'ftools'
6
+ require 'shooting_star/config'
7
+ require 'shooting_star/shooter'
8
+
9
+ module ShootingStar
10
+ VERSION = '1.0.3'
11
+ CONFIG = Config.new(
12
+ :config => 'config/shooting_star.yml',
13
+ :pid_file => 'log/shooting_star.pid',
14
+ :log_file => 'log/shooting_star.log',
15
+ :daemon => false,
16
+ :slient => false)
17
+
18
+ def self.configure(options = {})
19
+ if @log_file
20
+ @log_file.close
21
+ @log_file = nil
22
+ end
23
+ config_file = options[:config] || CONFIG.config
24
+ CONFIG.merge!(YAML.load_file(config_file)) if File.exist?(config_file)
25
+ CONFIG.merge!(options)
26
+ end
27
+
28
+ def self.shooter
29
+ @@shooter ||= DRbObject.new_with_uri(CONFIG.shooter.uri)
30
+ end
31
+
32
+ # install config file and plugin
33
+ def self.init
34
+ base_dir = CONFIG.directory || `pwd`.chop
35
+ config_dir = File.join(base_dir, 'config')
36
+ `mkdir -p #{config_dir}` unless File.exist? config_dir
37
+ config_file = File.join(config_dir, 'shooting_star.yml')
38
+ unless File.exist? config_file
39
+ open(config_file, 'w') do |file|
40
+ open(__FILE__) do |data|
41
+ data.gets("__END__\n")
42
+ file.write data.read
43
+ end
44
+ end
45
+ end
46
+ log_dir = File.join(base_dir, 'log')
47
+ `mkdir -p #{log_dir}` unless File.exist? log_dir
48
+ plugin_dir = File.join(base_dir, 'vendor/plugins')
49
+ `mkdir -p #{plugin_dir}` unless File.exist? plugin_dir
50
+ meteor_strike_dir = File.join(plugin_dir, 'meteor_strike')
51
+ unless File.exist?(meteor_strike_dir)
52
+ src_dir = File.join(File.dirname(__FILE__),
53
+ '../vendor/plugins/meteor_strike')
54
+ `cp -R #{src_dir} #{meteor_strike_dir}`
55
+ end
56
+ end
57
+
58
+ def self.start(&block)
59
+ if File.exist?(CONFIG.pid_file)
60
+ log 'shooting_star is already running.'
61
+ return
62
+ end
63
+ if CONFIG.daemon
64
+ Signal.trap(:ALRM){exit} and sleep if fork
65
+ Process.setsid
66
+ end
67
+ require 'shooting_star/shooter'
68
+ @@druby = DRb.start_service(CONFIG.shooter.uri, Shooter.new)
69
+ require 'shooting_star/server'
70
+ Asteroid::run(CONFIG.server.host, CONFIG.server.port, Server) do
71
+ File.open(CONFIG.pid_file, "w") do |file|
72
+ file.puts Process.pid
73
+ file.puts $command_line
74
+ end
75
+ Signal.trap(:INT) do
76
+ Asteroid::stop
77
+ @@druby.stop_service
78
+ log "shooting_star service stopped."
79
+ File.rm_f(CONFIG.pid_file)
80
+ end
81
+ Signal.trap(:EXIT) do
82
+ File.rm_f(CONFIG.pid_file)
83
+ @log_file.close if @log_file
84
+ end
85
+ log "shooting_star service started."
86
+ Process.kill(:ALRM, Process.ppid) if CONFIG.daemon
87
+ block.call if block
88
+ end
89
+ end
90
+
91
+ def self.stop
92
+ command = ''
93
+ File.open(CONFIG.pid_file) do |file|
94
+ Process.kill(:INT, pid = file.gets.to_i)
95
+ command = file.gets
96
+ end
97
+ Thread.pass while File.exist?(CONFIG.pid_file)
98
+ rescue Errno::ENOENT
99
+ log "shooting_star service is not running."
100
+ rescue Errno::ESRCH
101
+ File.unlink(CONFIG.pid_file)
102
+ ensure
103
+ return command
104
+ end
105
+
106
+ def self.restart
107
+ command = stop
108
+ Thread.pass while File.exist?(CONFIG.pid_file)
109
+ system(command)
110
+ end
111
+
112
+ def self.report
113
+ puts "#{'-' * 79}\nconnections channel name\n#{'-' * 79}"
114
+ total_connections = 0
115
+ shooter.channels.each do |channel|
116
+ count = shooter.count(channel)
117
+ puts "%11d %s" % [count, channel]
118
+ puts shooter.listeners(channel).join(',') if CONFIG.with_uid
119
+ puts shooter.signatures(channel).join(',') if CONFIG.with_sig
120
+ total_connections += count
121
+ end
122
+ puts "#{'-' * 79}\n%11d %s\n#{'-' * 79}" % [total_connections, 'TOTAL']
123
+ end
124
+
125
+ def self.timestamp
126
+ now = Time.now
127
+ "%d%06d" % [now.tv_sec, now.tv_usec]
128
+ end
129
+
130
+ private
131
+ def self.log(*arg, &block)
132
+ puts(*arg, &block) unless CONFIG.silent
133
+ @log_file ||= open(CONFIG.log_file, 'a')
134
+ @log_file.puts(*arg, &block) if @log_file
135
+ end
136
+ end
137
+ __END__
138
+ server:
139
+ host: 0.0.0.0
140
+ port: 8080
141
+ shooter:
142
+ uri: druby://0.0.0.0:7123
@@ -0,0 +1,60 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+ require 'thread'
3
+ require 'ext/asteroid'
4
+ require 'socket'
5
+
6
+ module Server
7
+ def receive_data(data)
8
+ send_data data
9
+ write_and_close
10
+ end
11
+
12
+ def post_init
13
+ $post_init_test = true
14
+ end
15
+
16
+ def unbind
17
+ $unbind_test = true
18
+ end
19
+ end
20
+
21
+ class ShootingStarTest < Test::Unit::TestCase
22
+ def setup
23
+ mutex = Mutex.new
24
+ mutex.lock
25
+ @thread = Thread.new do
26
+ Asteroid::run('127.0.0.1', 7124, Server) do
27
+ mutex.unlock
28
+ end
29
+ end
30
+ mutex.lock
31
+ end
32
+
33
+ def teardown
34
+ Asteroid::stop
35
+ @thread.join
36
+ end
37
+
38
+ def test_communication
39
+ c = TCPSocket.open('127.0.0.1', 7124)
40
+ c.write "test"
41
+ assert_equal "test", c.read
42
+ assert $post_init_test
43
+ end
44
+
45
+ def test_unbind
46
+ c = TCPSocket.open('127.0.0.1', 7124)
47
+ c.close
48
+ Thread.pass
49
+ assert $unbind_test
50
+ end
51
+
52
+ def test_broad_cast
53
+ c1 = TCPSocket.open('127.0.0.1', 7124)
54
+ c2 = TCPSocket.open('127.0.0.1', 7124)
55
+ c1.write "test1"
56
+ c2.write "test2"
57
+ assert_equal "test1", c1.read
58
+ assert_equal "test2", c2.read
59
+ end
60
+ end
@@ -0,0 +1,55 @@
1
+ require File.join(File.dirname(__FILE__), '../test_helper')
2
+ require 'shooting_star'
3
+ require 'socket'
4
+ require 'thread'
5
+
6
+ $command_line = 'echo "testing"'
7
+
8
+ class ShootingStarTest < Test::Unit::TestCase
9
+ def setup
10
+ @config = ShootingStar.configure :silent => true,
11
+ :pid_file => 'log/shooting_star.test.pid',
12
+ :log_file => 'log/shooting_star.test.log',
13
+ :server => {:host => '127.0.0.1', :port => 8081},
14
+ :shooter => {:uri => 'druby://127.0.0.1:7124'}
15
+ mutex = Mutex.new
16
+ mutex.lock
17
+ @thread = Thread.new{ShootingStar.start{mutex.unlock}}
18
+ mutex.lock
19
+ end
20
+
21
+ def teardown
22
+ ShootingStar.stop
23
+ File.rm_f(@config.pid_file)
24
+ File.rm_f @config.log_file
25
+ @thread.join
26
+ end
27
+
28
+ def test_connection
29
+ client = TCPSocket.open('127.0.0.1', 8081)
30
+ assert_not_nil client
31
+ send client, "GET", "test/channel"
32
+ end
33
+
34
+ def test_shooter_exists
35
+ shooter = ShootingStar.shooter
36
+ assert_not_nil shooter
37
+ end
38
+
39
+ def test_c10k_problem
40
+ bin = File.join(RAILS_ROOT, 'bin/test_c10k_problem')
41
+ src = File.join(File.dirname(__FILE__), 'test_c10k_problem.c')
42
+ if !File.exist?(bin) || File.mtime(src) > File.mtime(bin)
43
+ system "gcc #{src} -o #{bin}"
44
+ end
45
+ system bin
46
+ end
47
+
48
+ private
49
+ def send(client, method, path)
50
+ client.write "#{method} #{path} HTTP/1.1\n\r" +
51
+ "Host: #{@config.server.host}:#{@config.server.port}\n\r" +
52
+ "Keep-Alive: 300\n\r" +
53
+ "Connection: keep-alive\n\r\n\r"
54
+ end
55
+ end
@@ -0,0 +1,47 @@
1
+ #include <stdio.h>
2
+ #include <stdlib.h>
3
+ #include <sys/types.h>
4
+ #include <sys/socket.h>
5
+ #include <sys/epoll.h>
6
+ #include <netinet/in.h>
7
+
8
+ #define NUM_CLIENT (10000)
9
+
10
+ int main(int argc, char **argv){
11
+ int status = EXIT_SUCCESS;
12
+
13
+ /* fill sockattr */
14
+ struct sockaddr_in addr;
15
+ addr.sin_family = AF_INET;
16
+ addr.sin_port = htons(8081);
17
+ addr.sin_addr.s_addr = inet_addr("127.0.0.1");
18
+
19
+ int i, j, s[NUM_CLIENT];
20
+ for(i = 0; i < NUM_CLIENT; ++i){
21
+ /* create socket */
22
+ s[i] = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
23
+ int result = connect(s[i], (struct sockaddr*)&addr, sizeof(addr));
24
+ if(result != 0){
25
+ printf("failed to connect.");
26
+ for(j = 0; j < i; ++j){
27
+ close(s[j]);
28
+ }
29
+ return EXIT_FAILURE;
30
+ }
31
+
32
+ char hdr[] = "POST /chat/test HTTP/1.1\r\n";
33
+ char host[] = "Host: 127.0.0.1\r\n\r\n";
34
+ write(s[i], hdr, strlen(hdr));
35
+ write(s[i], host, strlen(host));
36
+
37
+ int n = i + 1;
38
+ if(n % 10 == 0) printf(".");
39
+ if(n % 100 == 0) printf(" %d connections\n", n);
40
+ }
41
+
42
+ /* close fd and exit */
43
+ for(i = 0; i < NUM_CLIENT; ++i){
44
+ close(s[i]);
45
+ }
46
+ return status;
47
+ }
@@ -0,0 +1,33 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3
+ require 'test_help'
4
+ begin
5
+ require 'redgreen'
6
+ rescue Exception
7
+ end
8
+ $: << File.join(RAILS_ROOT, 'ext')
9
+
10
+ class Test::Unit::TestCase
11
+ # Transactional fixtures accelerate your tests by wrapping each test method
12
+ # in a transaction that's rolled back on completion. This ensures that the
13
+ # test database remains unchanged so your fixtures don't have to be reloaded
14
+ # between every test method. Fewer database queries means faster tests.
15
+ #
16
+ # Read Mike Clark's excellent walkthrough at
17
+ # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
18
+ #
19
+ # Every Active Record database supports transactions except MyISAM tables
20
+ # in MySQL. Turn off transactional fixtures in this case; however, if you
21
+ # don't care one way or the other, switching from MyISAM to InnoDB tables
22
+ # is recommended.
23
+ self.use_transactional_fixtures = true
24
+
25
+ # Instantiated fixtures are slow, but give you @david where otherwise you
26
+ # would need people(:david). If you don't want to migrate your existing
27
+ # test cases which use the @david style and don't mind the speed hit (each
28
+ # instantiated fixtures translates to a database query per test method),
29
+ # then set this back to true.
30
+ self.use_instantiated_fixtures = false
31
+
32
+ # Add more helper methods to be used by all tests here...
33
+ end
@@ -0,0 +1,64 @@
1
+ $: << File.join(File.dirname(__FILE__), '../lib')
2
+ require 'test/unit'
3
+ require 'shooting_star'
4
+ require 'socket'
5
+ require 'thread'
6
+ require 'redgreen' rescue nil
7
+
8
+ COMMAND_LINE = 'echo "testing"'
9
+
10
+ class ShootingStarTest < Test::Unit::TestCase
11
+ def setup
12
+ @config = ShootingStar.configure(
13
+ :silent => true,
14
+ :server => {:host => '127.0.0.1', :port => 8080},
15
+ :shooter => {:uri => 'druby://127.0.0.1:7123'})
16
+ @thread = Thread.new do
17
+ ShootingStar.start
18
+ end
19
+ Thread.pass while !File.exist?(@config.pid_file)
20
+ @client = TCPSocket.open('127.0.0.1', 8080)
21
+ @shooter = ShootingStar.shooter
22
+ end
23
+
24
+ def teardown
25
+ ShootingStar.stop
26
+ @thread.join
27
+ File.rm_f(@config.pid_file)
28
+ end
29
+
30
+ def test_activation
31
+ assert_not_nil @thread
32
+ end
33
+
34
+ def test_shooter
35
+ assert_not_nil @shooter
36
+ end
37
+
38
+ def test_client
39
+ assert_not_nil @client
40
+ end
41
+
42
+ def test_disconnection
43
+ @client.close
44
+ end
45
+
46
+ def test_communication
47
+ mutex = Mutex.new
48
+ mutex.lock
49
+ thread = Thread.new do
50
+ #send 'GET', 'test_application/test_channel_name'
51
+ mutex.unlock
52
+ end
53
+ mutex.lock
54
+ thread.join
55
+ end
56
+
57
+ private
58
+ def send(method, path)
59
+ @client.write "#{method} #{path} HTTP/1.1\n" +
60
+ "Host: #{ShootingStar.host}:#{ShootingStar.port}\n" +
61
+ "Keep-Alive: 300\n" +
62
+ "Connection: keep-alive\n\n"
63
+ end
64
+ end