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/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,7 @@
1
+ require File.expand_path('../_lib', File.dirname(__FILE__))
2
+
3
+ module RubyshTest::Functional
4
+ class FunctionalTest < RubyshTest::Test; end
5
+ end
6
+
7
+ MiniTest::Unit.runner = MiniTest::Unit.new
@@ -0,0 +1,2 @@
1
+ #!/bin/sh
2
+ lsof -p "$$" -F f
@@ -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
@@ -0,0 +1,7 @@
1
+ require File.expand_path('../_lib', File.dirname(__FILE__))
2
+
3
+ module RubyshTest::Integration
4
+ class IntegrationTest < RubyshTest::Test; end
5
+ end
6
+
7
+ MiniTest::Unit.runner = MiniTest::Unit.new
@@ -0,0 +1,6 @@
1
+ require File.expand_path('../_lib', File.dirname(__FILE__))
2
+
3
+ module RubyshTest::Integration
4
+ class RubyshTest < IntegrationTest
5
+ end
6
+ 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,7 @@
1
+ require File.expand_path('../_lib', File.dirname(__FILE__))
2
+
3
+ module RubyshTest::Unit
4
+ class UnitTest < RubyshTest::Test; end
5
+ end
6
+
7
+ MiniTest::Unit.runner = MiniTest::Unit.new
@@ -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