rexec 1.4.1 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ gem "rake"
@@ -35,84 +35,79 @@ module RExec
35
35
  # end
36
36
  #
37
37
  # Server.daemonize
38
+ #
39
+ # The base directory specifies a path such that:
40
+ # working_directory = #{@@base_directory}/#{daemon_name}
41
+ # log_directory = #{working_directory}/log
42
+ # log_file_path = #{log_directory}/daemon.log
43
+ # runtime_directory = #{working_directory}/run
44
+ # process_file_path = #{runtime_directory}/daemon.pid
38
45
  class Base
39
- @@var_directory = nil
40
- @@log_directory = nil
41
- @@pid_directory = nil
46
+ # For a system-level daemon you might want to specify "/var"
47
+ @@base_directory = "."
42
48
 
43
49
  # Return the name of the daemon
44
50
  def self.daemon_name
45
51
  return name.gsub(/[^a-zA-Z0-9]+/, '-')
46
52
  end
47
53
 
48
- # Base directory for daemon log files / run files
49
- def self.var_directory
50
- @@var_directory || File.join("", "var")
51
- end
52
-
53
- # The directory the daemon will run in (Dir.chdir)
54
+ # The directory the daemon will run in.
54
55
  def self.working_directory
55
- var_directory
56
+ File.join(@@base_directory, daemon_name)
56
57
  end
57
58
 
58
- # Return the directory to store log files in
59
+ # Return the directory to store log files in.
59
60
  def self.log_directory
60
- @@log_directory || File.join(var_directory, "log", daemon_name)
61
+ File.join(working_directory, "log")
61
62
  end
62
63
 
63
- # Standard log file for errors
64
- def self.err_fn
65
- File.join(log_directory, "stderr.log")
64
+ # Standard log file for stdout and stderr.
65
+ def self.log_file_path
66
+ File.join(log_directory, "daemon.log")
66
67
  end
67
68
 
68
- # Standard log file for normal output
69
- def self.log_fn
70
- File.join(log_directory, "stdout.log")
69
+ # Runtime data directory for the daemon.
70
+ def self.runtime_directory
71
+ File.join(working_directory, "run")
71
72
  end
72
73
 
73
- # Standard location of pid file
74
- def self.pid_directory
75
- @@pid_directory || File.join(var_directory, "run", daemon_name)
74
+ # Standard location of process pid file.
75
+ def self.process_file_path
76
+ File.join(runtime_directory, "daemon.pid")
76
77
  end
77
78
 
78
- # Standard pid file
79
- def self.pid_fn
80
- File.join(pid_directory, "#{daemon_name}.pid")
81
- end
82
-
83
- # Mark the error log
84
- def self.mark_err_log
85
- fp = File.open(err_fn, "a")
86
- fp.puts "=== Error Log Opened @ #{Time.now.to_s} ==="
87
- fp.close
79
+ # Mark the output log.
80
+ def self.mark_log
81
+ File.open(log_file_path, "a") do |log_file|
82
+ log_file.puts "=== Log Marked @ #{Time.now.to_s} ==="
83
+ end
88
84
  end
89
85
 
90
- # Prints some information relating to daemon startup problems
91
- def self.tail_err_log(outp)
86
+ # Prints some information relating to daemon startup problems.
87
+ def self.tail_log(output)
92
88
  lines = []
93
89
 
94
- File.open(err_fn, "r") do |fp|
95
- fp.seek_end
90
+ File.open(error_log_path, "r") do |log_file|
91
+ log_file.seek_end
96
92
 
97
- fp.reverse_each_line do |line|
93
+ log_file.reverse_each_line do |line|
98
94
  lines << line
99
- break if line.match("=== Error Log") || line.match("=== Daemon Exception Backtrace")
95
+ break if line.match("=== Log Marked") || line.match("=== Daemon Exception Backtrace")
100
96
  end
101
97
  end
102
98
 
103
99
  lines.reverse_each do |line|
104
- outp.puts line
100
+ output.puts line
105
101
  end
106
102
  end
107
103
 
108
- # Check the last few lines of the log file to find out if
109
- # the daemon crashed.
104
+ # Check the last few lines of the log file to find out if the daemon crashed.
110
105
  def self.crashed?
111
- File.open(err_fn, "r") do |fp|
112
- fp.seek_end
106
+ File.open(error_log_path, "r") do |log_file|
107
+ log_file.seek_end
113
108
 
114
109
  count = 2
115
- fp.reverse_each_line do |line|
110
+ log_file.reverse_each_line do |line|
116
111
  return true if line.match("=== Daemon Crashed")
117
112
 
118
113
  count -= 1
@@ -146,12 +141,10 @@ module RExec
146
141
 
147
142
  # The main function to setup any environment required by the daemon
148
143
  def self.prefork
149
- @@var_directory = File.expand_path(@@var_directory) if @@var_directory
150
- @@log_directory = File.expand_path(@@log_directory) if @@log_directory
151
- @@pid_directory = File.expand_path(@@pid_directory) if @@pid_directory
144
+ @@base_directory = File.expand_path(@@base_directory) if @@base_directory
152
145
 
153
146
  FileUtils.mkdir_p(log_directory)
154
- FileUtils.mkdir_p(pid_directory)
147
+ FileUtils.mkdir_p(runtime_directory)
155
148
  end
156
149
 
157
150
  # The main function to start the daemon
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
19
  # THE SOFTWARE.
20
20
 
21
- require 'rexec/daemon/pidfile'
21
+ require 'rexec/daemon/process_file'
22
22
  require 'rexec/task'
23
23
 
24
24
  module RExec
@@ -28,21 +28,19 @@ module RExec
28
28
 
29
29
  # This module contains functionality related to starting and stopping the daemon, and code for processing command line input.
30
30
  module Controller
31
- # This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for
32
- # +start+, +stop+, +restart+ or +status+.
31
+ # This function is called from the daemon executable. It processes ARGV and checks whether the user is asking for `start`, `stop`, `restart`, `status`.
33
32
  def self.daemonize(daemon)
34
- #puts "Running in #{WorkingDirectory}, logs in #{LogDirectory}"
35
- case !ARGV.empty? && ARGV[0]
33
+ case ARGV.shift
36
34
  when 'start'
37
35
  start(daemon)
38
36
  status(daemon)
39
37
  when 'stop'
40
38
  stop(daemon)
41
39
  status(daemon)
42
- PidFile.cleanup(daemon)
40
+ ProcessFile.cleanup(daemon)
43
41
  when 'restart'
44
42
  stop(daemon)
45
- PidFile.cleanup(daemon)
43
+ ProcessFile.cleanup(daemon)
46
44
  start(daemon)
47
45
  status(daemon)
48
46
  when 'status'
@@ -57,7 +55,7 @@ module RExec
57
55
  def self.start(daemon)
58
56
  puts "Starting daemon..."
59
57
 
60
- case PidFile.status(daemon)
58
+ case ProcessFile.status(daemon)
61
59
  when :running
62
60
  $stderr.puts "Daemon already running!"
63
61
  return
@@ -66,26 +64,26 @@ module RExec
66
64
  else
67
65
  $stderr.puts "Daemon in unknown state! Will clear previous state and continue."
68
66
  status(daemon)
69
- PidFile.clear(daemon)
67
+ ProcessFile.clear(daemon)
70
68
  end
71
69
 
72
70
  daemon.prefork
73
- daemon.mark_err_log
71
+ daemon.mark_log
74
72
 
75
73
  fork do
76
74
  Process.setsid
77
75
  exit if fork
78
76
 
79
- PidFile.store(daemon, Process.pid)
77
+ ProcessFile.store(daemon, Process.pid)
80
78
 
81
79
  File.umask 0000
82
80
  Dir.chdir daemon.working_directory
83
81
 
84
82
  $stdin.reopen "/dev/null"
85
- $stdout.reopen daemon.log_fn, "a"
83
+ $stdout.reopen daemon.log_file_path, "a"
86
84
  $stdout.sync = true
87
85
 
88
- $stderr.reopen daemon.err_fn, "a"
86
+ $stderr.reopen $stdout
89
87
  $stderr.sync = true
90
88
 
91
89
  begin
@@ -130,7 +128,7 @@ module RExec
130
128
  puts "Waiting for daemon to start..."
131
129
  sleep 0.1
132
130
  timer = TIMEOUT
133
- pid = PidFile.recall(daemon)
131
+ pid = ProcessFile.recall(daemon)
134
132
 
135
133
  while pid == nil and timer > 0
136
134
  # Wait a moment for the forking to finish...
@@ -140,7 +138,7 @@ module RExec
140
138
  # If the daemon has crashed, it is never going to start...
141
139
  break if daemon.crashed?
142
140
 
143
- pid = PidFile.recall(daemon)
141
+ pid = ProcessFile.recall(daemon)
144
142
 
145
143
  timer -= 1
146
144
  end
@@ -148,15 +146,15 @@ module RExec
148
146
 
149
147
  # Prints out the status of the daemon
150
148
  def self.status(daemon)
151
- case PidFile.status(daemon)
149
+ case ProcessFile.status(daemon)
152
150
  when :running
153
- puts "Daemon status: running pid=#{PidFile.recall(daemon)}"
151
+ puts "Daemon status: running pid=#{ProcessFile.recall(daemon)}"
154
152
  when :unknown
155
153
  if daemon.crashed?
156
154
  puts "Daemon status: crashed"
157
155
 
158
156
  $stdout.flush
159
- daemon.tail_err_log($stderr)
157
+ daemon.tail_error_log($stderr)
160
158
  else
161
159
  puts "Daemon status: unknown"
162
160
  end
@@ -170,27 +168,27 @@ module RExec
170
168
  puts "Stopping daemon..."
171
169
 
172
170
  # Check if the pid file exists...
173
- if !File.file?(daemon.pid_fn)
171
+ unless File.file?(daemon.process_file_path)
174
172
  puts "Pid file not found. Is the daemon running?"
175
173
  return
176
174
  end
177
175
 
178
- pid = PidFile.recall(daemon)
176
+ pid = ProcessFile.recall(daemon)
179
177
 
180
178
  # Check if the daemon is already stopped...
181
- unless PidFile.running(daemon)
179
+ unless ProcessFile.running(daemon)
182
180
  puts "Pid #{pid} is not running. Has daemon crashed?"
183
181
  return
184
182
  end
185
183
 
186
- pid = PidFile.recall(daemon)
184
+ pid = ProcessFile.recall(daemon)
187
185
  Process.kill("INT", pid)
188
186
  sleep 0.1
189
187
 
190
188
  # Kill/Term loop - if the daemon didn't die easily, shoot
191
189
  # it a few more times.
192
190
  attempts = 5
193
- while PidFile.running(daemon) and attempts > 0
191
+ while ProcessFile.running(daemon) and attempts > 0
194
192
  sig = (attempts < 2) ? "KILL" : "TERM"
195
193
 
196
194
  puts "Sending #{sig} to pid #{pid}..."
@@ -201,13 +199,13 @@ module RExec
201
199
  end
202
200
 
203
201
  # If after doing our best the daemon is still running (pretty odd)...
204
- if PidFile.running(daemon)
202
+ if ProcessFile.running(daemon)
205
203
  puts "Daemon appears to be still running!"
206
204
  return
207
205
  end
208
206
 
209
207
  # Otherwise the daemon has been stopped.
210
- PidFile.clear(daemon)
208
+ ProcessFile.clear(daemon)
211
209
  end
212
210
  end
213
211
  end
@@ -21,21 +21,21 @@
21
21
  module RExec
22
22
  module Daemon
23
23
  # This module controls the storage and retrieval of process id files.
24
- module PidFile
24
+ module ProcessFile
25
25
  # Saves the pid for the given daemon
26
26
  def self.store(daemon, pid)
27
- File.open(daemon.pid_fn, 'w') {|f| f << pid}
27
+ File.open(daemon.process_file_path, 'w') {|f| f << pid}
28
28
  end
29
29
 
30
30
  # Retrieves the pid for the given daemon
31
31
  def self.recall(daemon)
32
- IO.read(daemon.pid_fn).to_i rescue nil
32
+ IO.read(daemon.process_file_path).to_i rescue nil
33
33
  end
34
34
 
35
35
  # Removes the pid saved for a particular daemon
36
36
  def self.clear(daemon)
37
- if File.exist? daemon.pid_fn
38
- FileUtils.rm(daemon.pid_fn)
37
+ if File.exist? daemon.process_file_path
38
+ FileUtils.rm(daemon.process_file_path)
39
39
  end
40
40
  end
41
41
 
@@ -58,8 +58,8 @@ module RExec
58
58
  # This function returns the status of the daemon. This can be one of +:running+, +:unknown+ (pid file exists but no
59
59
  # corresponding process can be found) or +:stopped+.
60
60
  def self.status(daemon)
61
- if File.exist? daemon.pid_fn
62
- return PidFile.running(daemon) ? :running : :unknown
61
+ if File.exist? daemon.process_file_path
62
+ return ProcessFile.running(daemon) ? :running : :unknown
63
63
  else
64
64
  return :stopped
65
65
  end
@@ -21,8 +21,8 @@
21
21
  module RExec
22
22
  module VERSION
23
23
  MAJOR = 1
24
- MINOR = 4
25
- TINY = 1
24
+ MINOR = 5
25
+ TINY = 0
26
26
 
27
27
  STRING = [MAJOR, MINOR, TINY].join('.')
28
28
  end
@@ -0,0 +1,10 @@
1
+
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ desc "Run tests"
9
+ task :default => :test
10
+
@@ -19,18 +19,18 @@
19
19
  # THE SOFTWARE.
20
20
 
21
21
  $connection.run do |object|
22
- case(object[0])
23
- when :bounce
24
- $stderr.puts("Bouncing #{object[1].inspect}...")
25
- $connection.send_object(object[1])
26
- when :exception
27
- $stderr.puts("Raising exception...")
28
- raise Exception.new("I love exceptions!")
29
- when :stop
30
- $stderr.puts("Stopping connection manually...")
31
- $connection.stop
32
- when :stderr
33
- $stderr.puts object[1]
34
- $stderr.flush
35
- end
22
+ case(object[0])
23
+ when :bounce
24
+ $stderr.puts("Bouncing #{object[1].inspect}...")
25
+ $connection.send_object(object[1])
26
+ when :exception
27
+ $stderr.puts("Raising exception...")
28
+ raise Exception.new("I love exceptions!")
29
+ when :stop
30
+ $stderr.puts("Stopping connection manually...")
31
+ $connection.stop
32
+ when :stderr
33
+ $stderr.puts object[1]
34
+ $stderr.flush
35
+ end
36
36
  end
@@ -20,8 +20,9 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'rubygems'
23
+ $LOAD_PATH.unshift File.expand_path("../../lib/", __FILE__)
24
24
 
25
+ require 'rubygems'
25
26
  require 'pathname'
26
27
 
27
28
  require 'rexec'
@@ -33,41 +34,39 @@ require 'xmlrpc/server'
33
34
 
34
35
  # Very simple XMLRPC daemon
35
36
  class TestDaemon < RExec::Daemon::Base
36
- @@var_directory = "/tmp/ruby-test/var"
37
+ @@var_directory = "/tmp/ruby-test/var"
37
38
 
38
- def self.run
39
- puts "Starting server..."
40
-
41
- @@rpc_server = WEBrick::HTTPServer.new(
42
- :Port => 11235,
43
- :BindAddress => "0.0.0.0",
44
- :SSLEnable => true,
45
- :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
46
- :SSLCertName => [["CN", WEBrick::Utils::getservername]])
47
-
48
- @@listener = XMLRPC::WEBrickServlet.new
49
-
50
- @@listener.add_handler("add") do |amount|
51
- @@count ||= 0
52
- @@count += amount
53
- end
54
-
55
- @@listener.add_handler("total") do
56
- @@count
57
- end
58
-
59
- @@rpc_server.mount("/RPC2", @@listener)
60
-
61
- $stdout.flush
62
- $stderr.flush
39
+ def self.run
40
+ puts "Starting server..."
63
41
 
64
- @@rpc_server.start
65
- end
66
-
67
- def self.shutdown
42
+ @@rpc_server = WEBrick::HTTPServer.new(
43
+ :Port => 31337,
44
+ :BindAddress => "0.0.0.0"
45
+ )
46
+
47
+ @@listener = XMLRPC::WEBrickServlet.new
48
+
49
+ @@listener.add_handler("add") do |amount|
50
+ @@count ||= 0
51
+ @@count += amount
52
+ end
53
+
54
+ @@listener.add_handler("total") do
55
+ @@count
56
+ end
57
+
58
+ @@rpc_server.mount("/RPC2", @@listener)
59
+
60
+ $stdout.flush
61
+ $stderr.flush
62
+
63
+ @@rpc_server.start
64
+ end
65
+
66
+ def self.shutdown
68
67
  puts "Shutting down server..."
69
- @@rpc_server.shutdown
70
- end
68
+ @@rpc_server.shutdown
69
+ end
71
70
  end
72
71
 
73
72
  TestDaemon.daemonize
@@ -0,0 +1,5 @@
1
+
2
+ $LOAD_PATH.unshift File.expand_path("../../lib/", __FILE__)
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
@@ -20,19 +20,20 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'rubygems'
23
+ require 'helper'
24
+
24
25
  require 'rexec'
25
26
 
26
27
  CLIENT = <<EOF
27
28
 
28
29
  $connection.run do |path|
29
- listing = []
30
+ listing = []
30
31
 
31
- IO.popen("ls -la " + path.dump, "r+") do |ls|
32
- listing = ls.readlines
33
- end
32
+ IO.popen("ls -la " + path.dump, "r+") do |ls|
33
+ listing = ls.readlines
34
+ end
34
35
 
35
- $connection.send_object(listing)
36
+ $connection.send_object(listing)
36
37
  end
37
38
 
38
39
  EOF
@@ -41,14 +42,14 @@ command = ARGV[0] || "ruby"
41
42
 
42
43
  puts "Starting server..."
43
44
  RExec::start_server(CLIENT, command) do |conn, pid|
44
- puts "Sending path..."
45
- conn.send_object("/")
45
+ puts "Sending path..."
46
+ conn.send_object("/")
46
47
 
47
- puts "Waiting for response..."
48
- listing = conn.receive_object
48
+ puts "Waiting for response..."
49
+ listing = conn.receive_object
49
50
 
50
- puts "Received listing:"
51
- listing.each do |entry|
52
- puts "\t#{entry}"
53
- end
51
+ puts "Received listing:"
52
+ listing.each do |entry|
53
+ puts "\t#{entry}"
54
+ end
54
55
  end
@@ -20,30 +20,28 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
- require 'rubygems'
23
+ require 'helper'
24
+
24
25
  require 'pathname'
25
26
  require 'xmlrpc/client'
26
- require 'test/unit'
27
27
 
28
28
  class DaemonTest < Test::Unit::TestCase
29
- DAEMON = (Pathname.new(__FILE__).dirname + "./daemon.rb").realpath
30
- def setup
31
- system(DAEMON, "start")
32
-
33
- # Daemon takes a moment to become fully operational
34
- sleep(5)
35
- end
36
-
37
- def teardown
38
- system(DAEMON, "stop")
39
- end
40
-
41
- def test_connection
42
- rpc = XMLRPC::Client.new_from_uri("https://localhost:11235")
43
- rpc.call("add", 10)
44
-
45
- total = rpc.call("total")
46
-
47
- assert_equal 10, total
48
- end
29
+ DAEMON = (Pathname.new(__FILE__).dirname + "./daemon.rb").realpath
30
+
31
+ def setup
32
+ system(DAEMON.to_s, "start")
33
+ end
34
+
35
+ def teardown
36
+ system(DAEMON.to_s, "stop")
37
+ end
38
+
39
+ def test_connection
40
+ rpc = XMLRPC::Client.new_from_uri("http://localhost:31337")
41
+ rpc.call("add", 10)
42
+
43
+ total = rpc.call("total")
44
+
45
+ assert_equal 10, total
46
+ end
49
47
  end
@@ -23,9 +23,8 @@
23
23
  # This script is used to test actual remote connections
24
24
  # e.g. ./test_remote.rb "ssh haru.oriontransfer.org"
25
25
 
26
- require 'rubygems'
26
+ require 'helper'
27
27
 
28
- require 'test/unit'
29
28
  require 'fileutils'
30
29
  require 'pathname'
31
30
  require 'rexec'
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'helper'
24
+
25
+ require 'fileutils'
26
+ require 'pathname'
27
+ require 'rexec'
28
+
29
+ class ServerTest < Test::Unit::TestCase
30
+ def test_local_execution
31
+ code = Pathname.new(__FILE__).dirname + "./client.rb"
32
+ sobj = [1, 2, "three", 4]
33
+ stderr_text = "There was no error.. maybe?"
34
+ connection_started = false
35
+ object_received = false
36
+
37
+ RExec::start_server(code.read, "ruby", :passthrough => []) do |conn, task|
38
+ connection_started = true
39
+ conn.send_object([:bounce, sobj])
40
+
41
+ assert_equal sobj, conn.receive_object
42
+
43
+ assert_raises(Exception) do
44
+ conn.send_object([:exception])
45
+ obj = conn.receive_object
46
+
47
+ puts "Received object which should have been exception: #{obj.inspect}"
48
+ end
49
+
50
+ conn.dump_errors
51
+ conn.send_object([:stderr, stderr_text])
52
+
53
+ puts "Attemping to read from #{conn.error.to_i}..."
54
+ assert_equal stderr_text, conn.error.readline.chomp
55
+
56
+ conn.stop
57
+ end
58
+
59
+ assert(connection_started, "Connection started")
60
+ end
61
+
62
+ def test_shell_execution
63
+ connection_started = false
64
+ code = Pathname.new(__FILE__).dirname + "client.rb"
65
+
66
+ test_obj = [1, 2, 3, 4, "five"]
67
+
68
+ RExec::start_server(code.read, "/bin/sh -c ruby", :passthrough => []) do |conn, task|
69
+ connection_started = true
70
+ conn.send_object([:bounce, test_obj])
71
+
72
+ assert_equal test_obj, conn.receive_object
73
+
74
+ conn.stop
75
+ end
76
+
77
+ assert(connection_started, "Connection started")
78
+ end
79
+
80
+ def test_shell_execution_non_block
81
+ connection_started = false
82
+ code = Pathname.new(__FILE__).dirname + "client.rb"
83
+
84
+ test_obj = [1, 2, 3, 4, "five"]
85
+
86
+ conn, task = RExec::start_server(code.read, "/bin/sh -c ruby", :passthrough => [])
87
+ conn.send_object([:bounce, test_obj])
88
+
89
+ assert_equal test_obj, conn.receive_object
90
+
91
+ conn.stop
92
+ end
93
+ end
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'helper'
24
+
25
+ require 'fileutils'
26
+ require 'pathname'
27
+ require 'rexec'
28
+ require 'timeout'
29
+
30
+ class TaskTest < Test::Unit::TestCase
31
+ TASK_PATH = Pathname.new(__FILE__).dirname + "./task.rb"
32
+ TEXT = "The quick brown fox jumped over the lazy dog."
33
+ STDOUT_TEXT = "STDOUT: " + TEXT
34
+ STDERR_TEXT = "STDERR: " + TEXT
35
+
36
+ def test_script_execution
37
+ RExec::Task.open(TASK_PATH) do |task|
38
+ task.input.puts(TEXT)
39
+ task.input.close
40
+
41
+ assert_equal STDOUT_TEXT, task.output.readline.chomp
42
+ assert_equal STDERR_TEXT, task.error.readline.chomp
43
+ end
44
+ end
45
+
46
+ def test_ruby_execution
47
+ RExec::Task.open(["ruby", '-']) do |task|
48
+ task.input.puts(TASK_PATH.read)
49
+ task.input.puts("\004")
50
+
51
+ task.input.puts(TEXT)
52
+ task.input.close
53
+
54
+ assert_equal STDOUT_TEXT, task.output.readline.chomp
55
+ assert_equal STDERR_TEXT, task.error.readline.chomp
56
+ end
57
+ end
58
+
59
+ def test_spawn_child
60
+ rd, wr = IO.pipe
61
+ pid = RExec::Task.spawn_child do
62
+ rd.close
63
+ wr.write(TEXT)
64
+ wr.close
65
+ exit(10)
66
+ end
67
+
68
+ wr.close
69
+
70
+ pid, status = Process.wait2(pid)
71
+
72
+ assert_equal rd.read, TEXT
73
+ assert_equal status, status
74
+ end
75
+
76
+ def test_spawn_daemon
77
+ rd, wr = IO.pipe
78
+
79
+ # We launch one daemon to start another. The first daemon will exit, but the second will keep on running.
80
+ ppid = RExec::Task.spawn_daemon do
81
+ RExec::Task.spawn_daemon do
82
+ rd.close
83
+ sleep 0.5
84
+ wr.puts(TEXT, Process.pid)
85
+ wr.close
86
+ sleep 0.5
87
+ end
88
+ end
89
+
90
+ wr.close
91
+
92
+ Timeout::timeout(5) do
93
+ until !RExec::Task.running?(ppid) do
94
+ sleep(0.1)
95
+ end
96
+
97
+ text = rd.readline.chomp
98
+ pid = rd.readline.chomp.to_i
99
+
100
+ assert_raises(EOFError) do
101
+ rd.readline
102
+ end
103
+
104
+ assert_equal text, TEXT
105
+ assert RExec::Task.running?(pid)
106
+
107
+ until !RExec::Task.running?(pid) do
108
+ sleep(0.1)
109
+ end
110
+ end
111
+ end
112
+
113
+ def test_task
114
+ test_results = Proc.new do |input, output, error|
115
+ input.puts TEXT
116
+ input.flush
117
+
118
+ assert_equal output.readline.chomp, STDOUT_TEXT
119
+ assert_equal error.readline.chomp, STDERR_TEXT
120
+ end
121
+
122
+ RExec::Task.open(TASK_PATH) do |task|
123
+ test_results.call(task.input, task.output, task.error)
124
+ end
125
+
126
+ rd, wr = IO.pipe
127
+
128
+ RExec::Task.open(TASK_PATH, :in => rd) do |task|
129
+ test_results.call(wr, task.output, task.error)
130
+ end
131
+
132
+ assert rd.closed?
133
+
134
+ assert_raises(Errno::EPIPE) do
135
+ wr.puts "The pipe is closed on the other side.."
136
+ end
137
+
138
+ in_rd, in_wr = IO.pipe
139
+ out_rd, out_wr = IO.pipe
140
+ err_rd, err_wr = IO.pipe
141
+
142
+ spawn_child_daemon = Proc.new do
143
+ task = RExec::Task.open(TASK_PATH, :in => in_rd, :out => out_wr, :err => err_wr, :daemonize => true)
144
+ end
145
+
146
+ task = RExec::Task.open(spawn_child_daemon, :daemonize => true)
147
+
148
+ until !task.running? do
149
+ sleep 0.1
150
+ end
151
+
152
+ test_results.call(in_wr, out_rd, err_rd)
153
+
154
+ assert !task.running?
155
+ end
156
+
157
+ def test_task_passthrough
158
+ script = "echo " + "Hello World!".dump + " | #{TASK_PATH.realpath.to_s.dump}"
159
+
160
+ RExec::Task.open(script, :passthrough => :all) do
161
+
162
+ end
163
+
164
+ [$stdin, $stdout, $stderr].each do |io|
165
+ assert !io.closed?
166
+ end
167
+ end
168
+ end
metadata CHANGED
@@ -1,38 +1,29 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rexec
3
- version: !ruby/object:Gem::Version
4
- hash: 5
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.5.0
5
5
  prerelease:
6
- segments:
7
- - 1
8
- - 4
9
- - 1
10
- version: 1.4.1
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Samuel Williams
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2011-08-09 00:00:00 Z
12
+ date: 2012-10-15 00:00:00.000000000 Z
19
13
  dependencies: []
20
-
21
14
  description:
22
- email: samuel.williams@oriontransfer.co.nz
23
- executables:
15
+ email: samuel@oriontransfer.org
16
+ executables:
24
17
  - daemon-exec
25
18
  extensions: []
26
-
27
19
  extra_rdoc_files: []
28
-
29
- files:
20
+ files:
30
21
  - bin/daemon-exec
31
22
  - lib/rexec/client.rb
32
23
  - lib/rexec/connection.rb
33
24
  - lib/rexec/daemon/base.rb
34
25
  - lib/rexec/daemon/controller.rb
35
- - lib/rexec/daemon/pidfile.rb
26
+ - lib/rexec/daemon/process_file.rb
36
27
  - lib/rexec/daemon.rb
37
28
  - lib/rexec/environment.rb
38
29
  - lib/rexec/priviledges.rb
@@ -44,49 +35,39 @@ files:
44
35
  - lib/rexec.rb
45
36
  - test/client.rb
46
37
  - test/daemon.rb
47
- - test/daemon_test.rb
38
+ - test/helper.rb
48
39
  - test/interrupt.rb
49
40
  - test/listing_example.rb
50
- - test/remote_server_test.rb
51
- - test/server_test.rb
52
41
  - test/task.rb
53
- - test/task_test.rb
42
+ - test/test_daemon.rb
43
+ - test/test_remote_server.rb
44
+ - test/test_server.rb
45
+ - test/test_task.rb
54
46
  - README.md
47
+ - rakefile.rb
48
+ - Gemfile
55
49
  homepage: http://www.oriontransfer.co.nz/gems/rexec
56
50
  licenses: []
57
-
58
51
  post_install_message:
59
52
  rdoc_options: []
60
-
61
- require_paths:
53
+ require_paths:
62
54
  - lib
63
- required_ruby_version: !ruby/object:Gem::Requirement
55
+ required_ruby_version: !ruby/object:Gem::Requirement
64
56
  none: false
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- hash: 3
69
- segments:
70
- - 0
71
- version: "0"
72
- required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
62
  none: false
74
- requirements:
75
- - - ">="
76
- - !ruby/object:Gem::Version
77
- hash: 3
78
- segments:
79
- - 0
80
- version: "0"
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
81
67
  requirements: []
82
-
83
68
  rubyforge_project:
84
- rubygems_version: 1.8.7
69
+ rubygems_version: 1.8.24
85
70
  signing_key:
86
71
  specification_version: 3
87
- summary: RExec (Remote Execution) is a tool to facilitate communicating to another ruby process and executing code.
88
- test_files:
89
- - test/daemon_test.rb
90
- - test/remote_server_test.rb
91
- - test/server_test.rb
92
- - test/task_test.rb
72
+ summary: RExec assists with and manages the execution of child and daemon tasks.
73
+ test_files: []
@@ -1,94 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
-
23
- require 'rubygems'
24
-
25
- require 'test/unit'
26
- require 'fileutils'
27
- require 'pathname'
28
- require 'rexec'
29
-
30
- class ServerTest < Test::Unit::TestCase
31
- def test_local_execution
32
- code = Pathname.new(__FILE__).dirname + "./client.rb"
33
- sobj = [1, 2, "three", 4]
34
- stderr_text = "There was no error.. maybe?"
35
- connection_started = false
36
- object_received = false
37
-
38
- RExec::start_server(code.read, "ruby", :passthrough => []) do |conn, task|
39
- connection_started = true
40
- conn.send_object([:bounce, sobj])
41
-
42
- assert_equal sobj, conn.receive_object
43
-
44
- assert_raises(Exception) do
45
- conn.send_object([:exception])
46
- obj = conn.receive_object
47
-
48
- puts "Received object which should have been exception: #{obj.inspect}"
49
- end
50
-
51
- conn.dump_errors
52
- conn.send_object([:stderr, stderr_text])
53
-
54
- puts "Attemping to read from #{conn.error.to_i}..."
55
- assert_equal stderr_text, conn.error.readline.chomp
56
-
57
- conn.stop
58
- end
59
-
60
- assert(connection_started, "Connection started")
61
- end
62
-
63
- def test_shell_execution
64
- connection_started = false
65
- code = Pathname.new(__FILE__).dirname + "client.rb"
66
-
67
- test_obj = [1, 2, 3, 4, "five"]
68
-
69
- RExec::start_server(code.read, "/bin/sh -c ruby", :passthrough => []) do |conn, task|
70
- connection_started = true
71
- conn.send_object([:bounce, test_obj])
72
-
73
- assert_equal test_obj, conn.receive_object
74
-
75
- conn.stop
76
- end
77
-
78
- assert(connection_started, "Connection started")
79
- end
80
-
81
- def test_shell_execution_non_block
82
- connection_started = false
83
- code = Pathname.new(__FILE__).dirname + "client.rb"
84
-
85
- test_obj = [1, 2, 3, 4, "five"]
86
-
87
- conn, task = RExec::start_server(code.read, "/bin/sh -c ruby", :passthrough => [])
88
- conn.send_object([:bounce, test_obj])
89
-
90
- assert_equal test_obj, conn.receive_object
91
-
92
- conn.stop
93
- end
94
- end
@@ -1,170 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- # Copyright (c) 2007, 2011 Samuel G. D. Williams. <http://www.oriontransfer.co.nz>
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the "Software"), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in
13
- # all copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- # THE SOFTWARE.
22
-
23
- require 'rubygems'
24
-
25
- require 'test/unit'
26
- require 'fileutils'
27
- require 'pathname'
28
- require 'rexec'
29
-
30
- require 'timeout'
31
-
32
- class TaskTest < Test::Unit::TestCase
33
- TASK_PATH = Pathname.new(__FILE__).dirname + "./task.rb"
34
- TEXT = "The quick brown fox jumped over the lazy dog."
35
- STDOUT_TEXT = "STDOUT: " + TEXT
36
- STDERR_TEXT = "STDERR: " + TEXT
37
-
38
- def test_script_execution
39
- RExec::Task.open(TASK_PATH) do |task|
40
- task.input.puts(TEXT)
41
- task.input.close
42
-
43
- assert_equal STDOUT_TEXT, task.output.readline.chomp
44
- assert_equal STDERR_TEXT, task.error.readline.chomp
45
- end
46
- end
47
-
48
- def test_ruby_execution
49
- RExec::Task.open("ruby") do |task|
50
- task.input.puts(TASK_PATH.read)
51
- task.input.puts("\004")
52
-
53
- task.input.puts(TEXT)
54
- task.input.close
55
-
56
- assert_equal STDOUT_TEXT, task.output.readline.chomp
57
- assert_equal STDERR_TEXT, task.error.readline.chomp
58
- end
59
- end
60
-
61
- def test_spawn_child
62
- rd, wr = IO.pipe
63
- pid = RExec::Task.spawn_child do
64
- rd.close
65
- wr.write(TEXT)
66
- wr.close
67
- exit(10)
68
- end
69
-
70
- wr.close
71
-
72
- pid, status = Process.wait2(pid)
73
-
74
- assert_equal rd.read, TEXT
75
- assert_equal status, status
76
- end
77
-
78
- def test_spawn_daemon
79
- rd, wr = IO.pipe
80
-
81
- # We launch one daemon to start another. The first daemon will exit, but the second will keep on running.
82
- ppid = RExec::Task.spawn_daemon do
83
- RExec::Task.spawn_daemon do
84
- rd.close
85
- sleep 0.5
86
- wr.puts(TEXT, Process.pid)
87
- wr.close
88
- sleep 0.5
89
- end
90
- end
91
-
92
- wr.close
93
-
94
- Timeout::timeout(5) do
95
- until !RExec::Task.running?(ppid) do
96
- sleep(0.1)
97
- end
98
-
99
- text = rd.readline.chomp
100
- pid = rd.readline.chomp.to_i
101
-
102
- assert_raises(EOFError) do
103
- rd.readline
104
- end
105
-
106
- assert_equal text, TEXT
107
- assert RExec::Task.running?(pid)
108
-
109
- until !RExec::Task.running?(pid) do
110
- sleep(0.1)
111
- end
112
- end
113
- end
114
-
115
- def test_task
116
- test_results = Proc.new do |input, output, error|
117
- input.puts TEXT
118
- input.flush
119
-
120
- assert_equal output.readline.chomp, STDOUT_TEXT
121
- assert_equal error.readline.chomp, STDERR_TEXT
122
- end
123
-
124
- RExec::Task.open(TASK_PATH) do |task|
125
- test_results.call(task.input, task.output, task.error)
126
- end
127
-
128
- rd, wr = IO.pipe
129
-
130
- RExec::Task.open(TASK_PATH, :in => rd) do |task|
131
- test_results.call(wr, task.output, task.error)
132
- end
133
-
134
- assert rd.closed?
135
-
136
- assert_raises(Errno::EINVAL) do
137
- wr.puts "The pipe is closed on the other side.."
138
- end
139
-
140
- in_rd, in_wr = IO.pipe
141
- out_rd, out_wr = IO.pipe
142
- err_rd, err_wr = IO.pipe
143
-
144
- spawn_child_daemon = Proc.new do
145
- task = RExec::Task.open(TASK_PATH, :in => in_rd, :out => out_wr, :err => err_wr, :daemonize => true)
146
- end
147
-
148
- task = RExec::Task.open(spawn_child_daemon, :daemonize => true)
149
-
150
- until !task.running? do
151
- sleep 0.1
152
- end
153
-
154
- test_results.call(in_wr, out_rd, err_rd)
155
-
156
- assert !task.running?
157
- end
158
-
159
- def test_task_passthrough
160
- script = "echo " + "Hello World!".dump + " | #{TASK_PATH.realpath.to_s.dump}"
161
-
162
- RExec::Task.open(script, :passthrough => :all) do
163
-
164
- end
165
-
166
- [$stdin, $stdout, $stderr].each do |io|
167
- assert !io.closed?
168
- end
169
- end
170
- end