rubysh 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/lib/rubysh/base_command.rb +65 -0
- data/lib/rubysh/base_directive.rb +24 -0
- data/lib/rubysh/command.rb +102 -0
- data/lib/rubysh/error.rb +20 -0
- data/lib/rubysh/fd.rb +43 -0
- data/lib/rubysh/pipe.rb +4 -0
- data/lib/rubysh/pipeline.rb +70 -0
- data/lib/rubysh/redirect.rb +181 -0
- data/lib/rubysh/runner.rb +156 -0
- data/lib/rubysh/subprocess/parallel_io.rb +184 -0
- data/lib/rubysh/subprocess/pipe_wrapper.rb +61 -0
- data/lib/rubysh/subprocess.rb +154 -0
- data/lib/rubysh/triple_less_than.rb +65 -0
- data/lib/rubysh/util.rb +55 -0
- data/lib/rubysh/version.rb +3 -0
- data/lib/rubysh.rb +149 -0
- data/rubysh.gemspec +26 -0
- data/test/_lib.rb +25 -0
- data/test/functional/_lib.rb +7 -0
- data/test/functional/lib/fd-lister +2 -0
- data/test/functional/lib/leaked_fds.rb +83 -0
- data/test/functional/lib/redirect_ordering.rb +15 -0
- data/test/functional/lib/triple_less_than.rb +16 -0
- data/test/integration/_lib.rb +7 -0
- data/test/integration/lib/rubysh.rb +6 -0
- data/test/rubysh +47 -0
- data/test/unit/_lib.rb +7 -0
- data/test/unit/lib/rubysh/command.rb +20 -0
- data/test/unit/lib/rubysh/pipeline.rb +108 -0
- data/test/unit/lib/rubysh/redirect.rb +44 -0
- data/test/unit/lib/rubysh/runner.rb +16 -0
- data/test/unit/lib/rubysh/subprocess/parallel_io.rb +233 -0
- data/test/unit/lib/rubysh/subprocess.rb +37 -0
- data/test/unit/lib/rubysh.rb +74 -0
- metadata +149 -0
data/lib/rubysh.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
require 'rubysh/version'
|
4
|
+
require 'rubysh/base_command'
|
5
|
+
require 'rubysh/base_directive'
|
6
|
+
require 'rubysh/command'
|
7
|
+
require 'rubysh/error'
|
8
|
+
require 'rubysh/fd'
|
9
|
+
require 'rubysh/pipeline'
|
10
|
+
require 'rubysh/redirect'
|
11
|
+
require 'rubysh/runner'
|
12
|
+
require 'rubysh/subprocess'
|
13
|
+
require 'rubysh/triple_less_than'
|
14
|
+
require 'rubysh/util'
|
15
|
+
|
16
|
+
# Command:
|
17
|
+
#
|
18
|
+
# Rubysh('ls', '/tmp')
|
19
|
+
# => Command: ls /tmp
|
20
|
+
# Rubysh('ls', '/tmp') | Rubysh('grep', 'myfile')
|
21
|
+
# => Command: ls /tmp | grep myfile
|
22
|
+
# Rubysh('ls', '/tmp', Rubysh.stderr > Rubysh.stdout)
|
23
|
+
# => Command: ls /tmp 2>&1
|
24
|
+
# Rubysh('ls', '/tmp', Rubysh.>('/tmp/outfile.txt'))
|
25
|
+
# => Command: ls /tmp > /tmp/outfile.txt
|
26
|
+
#
|
27
|
+
# TODO:
|
28
|
+
# => Command: (ls; ls) | grep foo
|
29
|
+
# => Command: echo <(ls /tmp)
|
30
|
+
# => Command: echo >(cat)
|
31
|
+
# something like the following to tee output:
|
32
|
+
# Rubysh('cat', Rubysh.stdout >> :pipe)
|
33
|
+
#
|
34
|
+
# If you want to capture output:
|
35
|
+
#
|
36
|
+
# Rubysh('cat', Rubysh.stdout > :pipe)
|
37
|
+
#
|
38
|
+
# Or for interactivity:
|
39
|
+
#
|
40
|
+
# q = Rubysh('cat', Rubysh.stdout > :stdout, Rubysh.stderr > :stderr, Rubysh.stdin < :stdin)
|
41
|
+
# q.write('my whole command')
|
42
|
+
# q.communicate # closes writeable pipes and reads from all readable pipes
|
43
|
+
# q.data_from(:stdout)
|
44
|
+
# q.data_from(1)
|
45
|
+
# q.status
|
46
|
+
#
|
47
|
+
# You can name pipes with whatever symbol name you want:
|
48
|
+
#
|
49
|
+
# Rubysh('cat', 2 > :stdout, 3 > :fd3)
|
50
|
+
#
|
51
|
+
# This API is a WIP:
|
52
|
+
#
|
53
|
+
# q.write('my first command') # write data (while also reading from readable pipes
|
54
|
+
# # in a select() loop)
|
55
|
+
# q.communicate(:partial => true) # read available data (don't close pipes)
|
56
|
+
# q.data_from(:stdout)
|
57
|
+
# q.write('my second command')
|
58
|
+
#
|
59
|
+
# Not sure this is needed:
|
60
|
+
# Rubysh('ls', '/tmp', Rubysh.&)
|
61
|
+
# => Command: ls /tmp &
|
62
|
+
|
63
|
+
# Either create a new Rubysh command:
|
64
|
+
#
|
65
|
+
# command = Rubysh('ls')
|
66
|
+
# command.run
|
67
|
+
#
|
68
|
+
# Or use the block syntax to create and run one:
|
69
|
+
#
|
70
|
+
# Rubysh {'ls'}
|
71
|
+
def Rubysh(*args, &blk)
|
72
|
+
if blk
|
73
|
+
raise Rubysh::Error::BaseError.new("Can't provide arguments and a block") if args.length > 0
|
74
|
+
command = blk.call
|
75
|
+
command = Rubysh::Command.new(command) unless command.kind_of?(Rubysh::Command)
|
76
|
+
command.run
|
77
|
+
else
|
78
|
+
Rubysh::Command.new(args)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module Rubysh
|
83
|
+
# Convenience methods
|
84
|
+
def self.run(command)
|
85
|
+
command = Rubysh::Command.new(command) unless command.kind_of?(Rubysh::Command)
|
86
|
+
command.run
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.Command(*args)
|
90
|
+
Command.new(*args)
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.Pipeline(*args)
|
94
|
+
Pipeline.new(*args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.FD(*args)
|
98
|
+
FD.new(*args)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.stdin
|
102
|
+
FD.new(0)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.stdout
|
106
|
+
FD.new(1)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.stderr
|
110
|
+
FD.new(2)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.>(target)
|
114
|
+
Redirect.new(1, '>', target)
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.>>(target)
|
118
|
+
Redirect.new(1, '>>', target)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.<(target)
|
122
|
+
Redirect.new(0, '<', target)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Hack to implement <<<
|
126
|
+
def self.<<(fd=nil)
|
127
|
+
fd ||= FD.new(0)
|
128
|
+
TripleLessThan::Shell.new(fd)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Internal utility methods
|
132
|
+
def self.log
|
133
|
+
unless @log
|
134
|
+
@log = Logger.new(STDERR)
|
135
|
+
@log.level = Logger::WARN
|
136
|
+
end
|
137
|
+
|
138
|
+
@log
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.assert(fact, msg, hard=false)
|
142
|
+
return if fact
|
143
|
+
|
144
|
+
msg = msg ? "Assertion Failure: #{msg}" : "Assertion Failure"
|
145
|
+
formatted = "#{msg}\n #{caller.join("\n ")}"
|
146
|
+
log.error(formatted)
|
147
|
+
raise msg if hard
|
148
|
+
end
|
149
|
+
end
|
data/rubysh.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/rubysh/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Greg Brockman"]
|
6
|
+
gem.email = ["gdb@gregbrockman.com"]
|
7
|
+
gem.description = "Rubysh: Ruby subprocesses made easy"
|
8
|
+
gem.summary = "Rubysh makes shelling out easy with a __sh__-like syntax layer for Ruby:
|
9
|
+
|
10
|
+
irb -r rubysh
|
11
|
+
>> command = Rubysh('echo', 'hello-from-Rubysh') | Rubysh('grep', '--color', 'Rubysh')
|
12
|
+
>> command.run
|
13
|
+
hello-from-Rubysh
|
14
|
+
=> Rubysh::Runner: echo hello-from-Rubysh | grep --color Rubysh (exitstatus: 0)"
|
15
|
+
gem.homepage = ""
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($\)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.name = "rubysh"
|
21
|
+
gem.require_paths = ["lib"]
|
22
|
+
gem.version = Rubysh::VERSION
|
23
|
+
|
24
|
+
gem.add_development_dependency 'minitest', '3.1.0'
|
25
|
+
gem.add_development_dependency 'mocha'
|
26
|
+
end
|
data/test/_lib.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'minitest/autorun'
|
5
|
+
require 'minitest/spec'
|
6
|
+
require 'mocha'
|
7
|
+
|
8
|
+
$:.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
|
9
|
+
|
10
|
+
require 'rubysh'
|
11
|
+
|
12
|
+
module RubyshTest
|
13
|
+
class Test < ::MiniTest::Spec
|
14
|
+
def setup
|
15
|
+
# Put any stubs here that you want to apply globally
|
16
|
+
end
|
17
|
+
|
18
|
+
def stub_pipe
|
19
|
+
read_fd = stub(:fcntl => nil, :read => nil, :close => nil, :closed? => true)
|
20
|
+
write_fd = stub(:fcntl => nil, :write => nil, :close => nil, :closed? => true)
|
21
|
+
IO.stubs(:pipe => [read_fd, write_fd])
|
22
|
+
[read_fd, write_fd]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path('../_lib', File.dirname(__FILE__))
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module RubyshTest::Functional
|
5
|
+
class LeakedFDsTest < FunctionalTest
|
6
|
+
# Try to remove inteference from other tests
|
7
|
+
def close_high_fds
|
8
|
+
begin
|
9
|
+
(3..20).each do |fd|
|
10
|
+
begin
|
11
|
+
io = IO.new(fd)
|
12
|
+
rescue Errno::EBADF
|
13
|
+
else
|
14
|
+
io.close
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_lsof(stdout)
|
21
|
+
pids = []
|
22
|
+
stdout.split("\n").each do |line|
|
23
|
+
pids << $1.to_i if line =~ /\Af(\d+)\Z/
|
24
|
+
end
|
25
|
+
pids
|
26
|
+
end
|
27
|
+
|
28
|
+
before do
|
29
|
+
close_high_fds
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'when spawning with no pipe' do
|
33
|
+
it 'has no unexpected FDs, post-exec' do
|
34
|
+
cmd = Rubysh(File.expand_path('fd-lister', File.dirname(__FILE__)), Rubysh.stdout > :stdout)
|
35
|
+
result = cmd.run
|
36
|
+
|
37
|
+
stdout = result.data(:stdout)
|
38
|
+
pids = parse_lsof(stdout)
|
39
|
+
assert_equal([0, 1, 2, 255], pids)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'when spawning with a redirect' do
|
44
|
+
it 'has no unexpected FDs, post-exec' do
|
45
|
+
cmd = Rubysh(File.expand_path('fd-lister', File.dirname(__FILE__)), Rubysh.stderr > '/dev/null', Rubysh.stdout > :stdout)
|
46
|
+
result = cmd.run
|
47
|
+
|
48
|
+
stdout = result.data(:stdout)
|
49
|
+
pids = parse_lsof(stdout)
|
50
|
+
assert_equal([0, 1, 2, 255], pids)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'when spawning with a pipe' do
|
55
|
+
it 'has no unexpected FDs, post-fork' do
|
56
|
+
cmd = Rubysh(File.expand_path('fd-lister', File.dirname(__FILE__))) | Rubysh('cat', Rubysh.stdout > :stdout)
|
57
|
+
result = cmd.run
|
58
|
+
|
59
|
+
stdout = result.data(:stdout)
|
60
|
+
pids = parse_lsof(stdout)
|
61
|
+
assert_equal([0, 1, 2, 255], pids)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'has no unexpected FDs, post-fork, when on the right side of a pipe' do
|
65
|
+
cmd = Rubysh('echo') | Rubysh(File.expand_path('fd-lister', File.dirname(__FILE__)), Rubysh.stdout > :stdout)
|
66
|
+
result = cmd.run
|
67
|
+
|
68
|
+
stdout = result.data(:stdout)
|
69
|
+
pids = parse_lsof(stdout)
|
70
|
+
assert_equal([0, 1, 2, 255], pids)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'has no unexpected FDs, post-fork, when in the middle of two pipes' do
|
74
|
+
cmd = Rubysh('echo') | Rubysh(File.expand_path('fd-lister', File.dirname(__FILE__))) | Rubysh('cat', Rubysh.stdout > :stdout)
|
75
|
+
result = cmd.run
|
76
|
+
|
77
|
+
stdout = result.data(:stdout)
|
78
|
+
pids = parse_lsof(stdout)
|
79
|
+
assert_equal([0, 1, 2, 255], pids)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Functional
|
4
|
+
class RedirectOrderingTest < FunctionalTest
|
5
|
+
describe 'when redirecting within a pipeline' do
|
6
|
+
it 'the internal redirect should win' do
|
7
|
+
cmd = Rubysh('echo', 'whoops!', Rubysh.stdout > '/dev/null') | Rubysh('cat', Rubysh.stdout > :stdout)
|
8
|
+
result = cmd.run
|
9
|
+
|
10
|
+
output = result.data(:stdout)
|
11
|
+
assert_equal('', output)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Functional
|
4
|
+
class TripleLessThanTest < FunctionalTest
|
5
|
+
describe 'when using <<< string' do
|
6
|
+
it 'the string is delivered on stdin' do
|
7
|
+
cmd = Rubysh('cat', Rubysh.stdout > :stdout, Rubysh.<<< 'test')
|
8
|
+
result = cmd.run
|
9
|
+
|
10
|
+
assert_equal(0, result.exitstatus)
|
11
|
+
output = result.data(:stdout)
|
12
|
+
assert_equal('test', output)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/test/rubysh
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Logfile created on Fri Sep 07 20:59:21 -0500 2012 by logger.rb/1.2.6
|
2
|
+
E, [2012-09-07T20:59:21.055389 #28117] ERROR -- : Assertion Failure: Reader should already be closed
|
3
|
+
/Users/gdb/projects/rubysh/lib/rubysh/subprocess/pipe_wrapper.rb:27:in `dump_yaml_and_close'
|
4
|
+
/Users/gdb/projects/rubysh/lib/rubysh/subprocess.rb:85:in `do_exec'
|
5
|
+
./unit/lib/rubysh/subprocess.rb:10:in `send'
|
6
|
+
./unit/lib/rubysh/subprocess.rb:10:in `test_0001_calls exec with the expected arguments'
|
7
|
+
/Library/Ruby/Gems/1.8/gems/mocha-0.12.1/lib/mocha/integration/mini_test/version_2112_to_320.rb:32:in `run_test'
|
8
|
+
/Library/Ruby/Gems/1.8/gems/mocha-0.12.1/lib/mocha/integration/mini_test/version_2112_to_320.rb:32:in `run'
|
9
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:823:in `_run_suite'
|
10
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:815:in `map'
|
11
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:815:in `_run_suite'
|
12
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `_run_suites'
|
13
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `map'
|
14
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `_run_suites'
|
15
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:775:in `_run_anything'
|
16
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:964:in `run_tests'
|
17
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:951:in `send'
|
18
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:951:in `_run'
|
19
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:950:in `each'
|
20
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:950:in `_run'
|
21
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:939:in `run'
|
22
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:697:in `autorun'
|
23
|
+
./unit/lib/rubysh/subprocess.rb:5
|
24
|
+
E, [2012-09-07T21:00:24.936576 #28135] ERROR -- : Assertion Failure: Reader should already be closed
|
25
|
+
/Users/gdb/projects/rubysh/lib/rubysh/subprocess/pipe_wrapper.rb:27:in `dump_yaml_and_close'
|
26
|
+
/Users/gdb/projects/rubysh/lib/rubysh/subprocess.rb:85:in `do_exec'
|
27
|
+
./unit/lib/rubysh/subprocess.rb:11:in `send'
|
28
|
+
./unit/lib/rubysh/subprocess.rb:11:in `test_0001_calls exec with the expected arguments'
|
29
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:342:in `assert_raises'
|
30
|
+
./unit/lib/rubysh/subprocess.rb:10:in `test_0001_calls exec with the expected arguments'
|
31
|
+
/Library/Ruby/Gems/1.8/gems/mocha-0.12.1/lib/mocha/integration/mini_test/version_2112_to_320.rb:32:in `run_test'
|
32
|
+
/Library/Ruby/Gems/1.8/gems/mocha-0.12.1/lib/mocha/integration/mini_test/version_2112_to_320.rb:32:in `run'
|
33
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:823:in `_run_suite'
|
34
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:815:in `map'
|
35
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:815:in `_run_suite'
|
36
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `_run_suites'
|
37
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `map'
|
38
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:802:in `_run_suites'
|
39
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:775:in `_run_anything'
|
40
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:964:in `run_tests'
|
41
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:951:in `send'
|
42
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:951:in `_run'
|
43
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:950:in `each'
|
44
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:950:in `_run'
|
45
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:939:in `run'
|
46
|
+
/Library/Ruby/Gems/1.8/gems/minitest-3.1.0/lib/minitest/unit.rb:697:in `autorun'
|
47
|
+
./unit/lib/rubysh/subprocess.rb:5
|
data/test/unit/_lib.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path('../../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Unit
|
4
|
+
class CommandTest < UnitTest
|
5
|
+
describe 'when instantiating a command' do
|
6
|
+
it 'parses out args and directives properly' do
|
7
|
+
directive = Rubysh.stderr > Rubysh.stdout
|
8
|
+
command = Rubysh::Command.new(['ls', '/tmp', directive, '/foo'])
|
9
|
+
assert_equal(['ls', '/tmp', '/foo'], command.args)
|
10
|
+
assert_equal([directive], command.directives)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'prints correctly' do
|
14
|
+
directive = Rubysh.stderr > Rubysh.stdout
|
15
|
+
command = Rubysh::Command.new(['ls', '/tmp', directive, '/foo'])
|
16
|
+
assert_equal('Command: ls /tmp 2>&1 /foo', command.to_s)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require File.expand_path('../../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Unit
|
4
|
+
class PipelineTest < UnitTest
|
5
|
+
describe 'when running a pipeline' do
|
6
|
+
it 'correctly sets up extra redirects for the beginning of the pipeline' do
|
7
|
+
subprocess = stub(:run => nil)
|
8
|
+
Rubysh::Subprocess.stubs(:new => subprocess)
|
9
|
+
|
10
|
+
command1 = Rubysh::Command.new(['ls', '/tmp'])
|
11
|
+
command2 = Rubysh::Command.new(['grep', 'foo'])
|
12
|
+
pipeline = Rubysh::Pipeline.new([command1, command2])
|
13
|
+
|
14
|
+
runner = Rubysh::Runner.new(pipeline)
|
15
|
+
runner.run_async
|
16
|
+
|
17
|
+
extra_directives1 = runner.state(command1)[:extra_directives]
|
18
|
+
|
19
|
+
assert_equal(1, extra_directives1.length)
|
20
|
+
|
21
|
+
redirect = extra_directives1[0]
|
22
|
+
|
23
|
+
assert_equal('>', redirect.direction)
|
24
|
+
assert_equal(Rubysh::FD.new(1), redirect.source)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'correctly sets up extra redirects for the end of the pipeline' do
|
28
|
+
subprocess = stub(:run => nil)
|
29
|
+
Rubysh::Subprocess.stubs(:new => subprocess)
|
30
|
+
|
31
|
+
command1 = Rubysh::Command.new(['ls', '/tmp'])
|
32
|
+
command2 = Rubysh::Command.new(['grep', 'foo'])
|
33
|
+
pipeline = Rubysh::Pipeline.new([command1, command2])
|
34
|
+
|
35
|
+
runner = Rubysh::Runner.new(pipeline)
|
36
|
+
runner.run_async
|
37
|
+
|
38
|
+
extra_directives2 = runner.state(command2)[:extra_directives]
|
39
|
+
|
40
|
+
assert_equal(1, extra_directives2.length)
|
41
|
+
|
42
|
+
redirect = extra_directives2[0]
|
43
|
+
|
44
|
+
assert_equal('<', redirect.direction)
|
45
|
+
assert_equal(Rubysh::FD.new(0), redirect.source)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'correctly sets up extra redirects for the middle of the pipeline' do
|
49
|
+
subprocess = stub(:run => nil)
|
50
|
+
Rubysh::Subprocess.stubs(:new => subprocess)
|
51
|
+
|
52
|
+
command1 = Rubysh::Command.new(['ls', '/tmp'])
|
53
|
+
command2 = Rubysh::Command.new(['grep', 'foo'])
|
54
|
+
command3 = Rubysh::Command.new(['cat'])
|
55
|
+
pipeline = Rubysh::Pipeline.new([command1, command2, command3])
|
56
|
+
|
57
|
+
runner = Rubysh::Runner.new(pipeline)
|
58
|
+
runner.run_async
|
59
|
+
|
60
|
+
extra_directives2 = runner.state(command2)[:extra_directives]
|
61
|
+
|
62
|
+
assert_equal(2, extra_directives2.length)
|
63
|
+
|
64
|
+
first_redirect = extra_directives2[0]
|
65
|
+
assert_equal('<', first_redirect.direction)
|
66
|
+
assert_equal(Rubysh::FD.new(0), first_redirect.source)
|
67
|
+
|
68
|
+
second_redirect = extra_directives2[1]
|
69
|
+
assert_equal('>', second_redirect.direction)
|
70
|
+
assert_equal(Rubysh::FD.new(1), second_redirect.source)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'instantiates the subprocess objects with expected arguments' do
|
74
|
+
subprocess = stub(:run => nil)
|
75
|
+
Rubysh::Subprocess.expects(:new).once.with do |args, directives, post_forks|
|
76
|
+
args == ['ls', '/tmp'] &&
|
77
|
+
directives.length == 1 &&
|
78
|
+
directives[0].direction == '>' &&
|
79
|
+
directives[0].source == Rubysh::FD.new(1)
|
80
|
+
end.returns(subprocess)
|
81
|
+
|
82
|
+
Rubysh::Subprocess.expects(:new).once.with do |args, directives, post_forks|
|
83
|
+
args == ['grep', 'foo'] &&
|
84
|
+
directives.length == 2 &&
|
85
|
+
directives[0].direction == '<' &&
|
86
|
+
directives[0].source == Rubysh::FD.new(0) &&
|
87
|
+
directives[1].direction == '>' &&
|
88
|
+
directives[1].source == Rubysh::FD.new(1)
|
89
|
+
end.returns(subprocess)
|
90
|
+
|
91
|
+
Rubysh::Subprocess.expects(:new).once.with do |args, directives, post_forks|
|
92
|
+
args = ['cat'] &&
|
93
|
+
directives.length == 1 &&
|
94
|
+
directives[0].direction == '<' &&
|
95
|
+
directives[0].source == Rubysh::FD.new(0)
|
96
|
+
end.returns(subprocess)
|
97
|
+
|
98
|
+
command1 = Rubysh::Command.new(['ls', '/tmp'])
|
99
|
+
command2 = Rubysh::Command.new(['grep', 'foo'])
|
100
|
+
command3 = Rubysh::Command.new(['cat'])
|
101
|
+
pipeline = Rubysh::Pipeline.new([command1, command2, command3])
|
102
|
+
|
103
|
+
runner = Rubysh::Runner.new(pipeline)
|
104
|
+
runner.run_async
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require File.expand_path('../../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Unit
|
4
|
+
class RedirectTest < UnitTest
|
5
|
+
describe 'when redirecting 2>&1' do
|
6
|
+
it 'correctly coerces when called with IO objects' do
|
7
|
+
runner = mock
|
8
|
+
stdout = stub(:fileno => 1)
|
9
|
+
stderr = stub(:fileno => 2)
|
10
|
+
|
11
|
+
stdout.stubs(:kind_of?).with(Integer).returns(false)
|
12
|
+
stdout.stubs(:kind_of?).with(IO).returns(true)
|
13
|
+
stderr.stubs(:kind_of?).with(String).returns(false)
|
14
|
+
stderr.stubs(:kind_of?).with(IO).returns(true)
|
15
|
+
|
16
|
+
# Due to stubbing
|
17
|
+
IO.expects(:new).never
|
18
|
+
|
19
|
+
redirect = Rubysh::Redirect.new(stderr, '>', stdout)
|
20
|
+
Rubysh::Util.expects(:dup2).once.with(1, 2)
|
21
|
+
Rubysh::Util.expects(:set_cloexec).once.with(2, false)
|
22
|
+
|
23
|
+
redirect.apply!(runner)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe 'when redirecting an unopened FD 3>&1' do
|
28
|
+
it 'applies dup2 as expected' do
|
29
|
+
runner = mock
|
30
|
+
stdout = stub(:fileno => 1)
|
31
|
+
|
32
|
+
IO.expects(:new).with(1).returns(stdout)
|
33
|
+
|
34
|
+
stdout = stub(:fileno => 1)
|
35
|
+
|
36
|
+
redirect = Rubysh::Redirect.new(3, '>', 1)
|
37
|
+
Rubysh::Util.expects(:dup2).once.with(1, 3)
|
38
|
+
Rubysh::Util.expects(:set_cloexec).once.with(3, false)
|
39
|
+
|
40
|
+
redirect.apply!(runner)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../../_lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module RubyshTest::Unit
|
4
|
+
class RunnerTest < UnitTest
|
5
|
+
describe 'when setting up parallel_io' do
|
6
|
+
it 'has the expected readers/writers' do
|
7
|
+
read_fd, write_fd = stub_pipe
|
8
|
+
command = Rubysh('ls', Rubysh.stderr > :stderr, Rubysh.stdin < :stdin)
|
9
|
+
runner = Rubysh::Runner.new(command)
|
10
|
+
|
11
|
+
assert_equal({read_fd => :stderr}, runner.readers)
|
12
|
+
assert_equal({write_fd => :stdin}, runner.writers)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|