rubysh 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|