blue-shell 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/LICENSE +14 -0
- data/Rakefile +7 -0
- data/lib/blue-shell.rb +6 -0
- data/lib/blue-shell/buffered_reader_expector.rb +73 -0
- data/lib/blue-shell/errors.rb +12 -0
- data/lib/blue-shell/matchers.rb +13 -0
- data/lib/blue-shell/matchers/exit_code_matcher.rb +40 -0
- data/lib/blue-shell/matchers/output_matcher.rb +38 -0
- data/lib/blue-shell/runner.rb +98 -0
- data/lib/blue-shell/version.rb +3 -0
- data/spec/assets/input.rb +6 -0
- data/spec/assets/pause.rb +5 -0
- data/spec/matchers/exit_code_matcher_spec.rb +76 -0
- data/spec/matchers/output_matcher_spec.rb +79 -0
- data/spec/matchers_spec.rb +31 -0
- data/spec/runner_spec.rb +194 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/helpers.rb +7 -0
- metadata +122 -0
data/LICENSE
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
Copyright (c) 2013 Pivotal Labs
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
5
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
6
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
7
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
8
|
+
|
9
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
10
|
+
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
12
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
13
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
14
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/lib/blue-shell.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module BlueShell
|
2
|
+
class BufferedReaderExpector
|
3
|
+
attr_reader :output
|
4
|
+
|
5
|
+
def initialize(out, debug = false)
|
6
|
+
@out = out
|
7
|
+
@debug = debug
|
8
|
+
@unused = ""
|
9
|
+
@output = ""
|
10
|
+
end
|
11
|
+
|
12
|
+
def expect(pattern, timeout = 5)
|
13
|
+
buffer = ''
|
14
|
+
|
15
|
+
case pattern
|
16
|
+
when String
|
17
|
+
pattern = Regexp.new(Regexp.quote(pattern))
|
18
|
+
when Regexp
|
19
|
+
else
|
20
|
+
raise TypeError, "unsupported pattern class: #{pattern.class}"
|
21
|
+
end
|
22
|
+
|
23
|
+
result = nil
|
24
|
+
position = 0
|
25
|
+
@unused ||= ""
|
26
|
+
|
27
|
+
while true
|
28
|
+
if !@unused.empty?
|
29
|
+
c = @unused.slice!(0).chr
|
30
|
+
elsif output_ended?(timeout)
|
31
|
+
@unused = buffer
|
32
|
+
break
|
33
|
+
else
|
34
|
+
c = @out.getc.chr
|
35
|
+
end
|
36
|
+
|
37
|
+
STDOUT.putc c if @debug
|
38
|
+
|
39
|
+
# wear your flip flops
|
40
|
+
unless (c == "\e") .. (c == "m")
|
41
|
+
if c == "\b"
|
42
|
+
if position > 0 && buffer[position - 1] && buffer[position - 1].chr != "\n"
|
43
|
+
position -= 1
|
44
|
+
end
|
45
|
+
else
|
46
|
+
if buffer.size > position
|
47
|
+
buffer[position] = c
|
48
|
+
else
|
49
|
+
buffer << c
|
50
|
+
end
|
51
|
+
|
52
|
+
position += 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
if matches = pattern.match(buffer)
|
57
|
+
result = [buffer, *matches.to_a[1..-1]]
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@output << buffer
|
63
|
+
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def output_ended?(timeout)
|
70
|
+
(@out.is_a?(IO) && !IO.select([@out], nil, nil, timeout)) || @out.eof?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module BlueShell
|
2
|
+
module Matchers
|
3
|
+
def say(expected_output, timeout = 30)
|
4
|
+
OutputMatcher.new(expected_output, timeout)
|
5
|
+
end
|
6
|
+
|
7
|
+
def have_exited_with(expected_code)
|
8
|
+
ExitCodeMatcher.new(expected_code)
|
9
|
+
end
|
10
|
+
|
11
|
+
alias :exit_with :have_exited_with
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module BlueShell
|
2
|
+
module Matchers
|
3
|
+
class ExitCodeMatcher
|
4
|
+
def initialize(expected_code)
|
5
|
+
@expected_code = expected_code
|
6
|
+
end
|
7
|
+
|
8
|
+
def matches?(runner)
|
9
|
+
raise Errors::InvalidInputError unless runner.respond_to?(:exit_code)
|
10
|
+
|
11
|
+
begin
|
12
|
+
Timeout.timeout(5) do
|
13
|
+
@actual_code = runner.exit_code
|
14
|
+
end
|
15
|
+
|
16
|
+
@actual_code == @expected_code
|
17
|
+
rescue Timeout::Error
|
18
|
+
@timed_out = true
|
19
|
+
false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message
|
24
|
+
if @timed_out
|
25
|
+
"expected process to exit with status #@expected_code, but it did not exit within 5 seconds"
|
26
|
+
else
|
27
|
+
"expected process to exit with status #{@expected_code}, but it exited with status #{@actual_code}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def negative_failure_message
|
32
|
+
if @timed_out
|
33
|
+
"expected process to exit with status #@expected_code, but it did not exit within 5 seconds"
|
34
|
+
else
|
35
|
+
"expected process to not exit with status #{@expected_code}, but it did"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module BlueShell
|
2
|
+
module Matchers
|
3
|
+
class OutputMatcher
|
4
|
+
attr_reader :timeout
|
5
|
+
|
6
|
+
def initialize(expected_output, timeout = 30)
|
7
|
+
@expected_output = expected_output
|
8
|
+
@timeout = timeout
|
9
|
+
end
|
10
|
+
|
11
|
+
def matches?(runner)
|
12
|
+
raise Errors::InvalidInputError unless runner.respond_to?(:expect)
|
13
|
+
@matched = runner.expect(@expected_output, @timeout)
|
14
|
+
@full_output = runner.output
|
15
|
+
!!@matched
|
16
|
+
end
|
17
|
+
|
18
|
+
def failure_message
|
19
|
+
if @expected_output.is_a?(Hash)
|
20
|
+
expected_keys = @expected_output.keys.map{|key| "'#{key}'"}.join(', ')
|
21
|
+
"expected one of #{expected_keys} to be printed, but it wasn't. full output:\n#@full_output"
|
22
|
+
else
|
23
|
+
"expected '#{@expected_output}' to be printed, but it wasn't. full output:\n#@full_output"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def negative_failure_message
|
28
|
+
if @expected_output.is_a?(Hash)
|
29
|
+
match = @matched
|
30
|
+
else
|
31
|
+
match = @expected_output
|
32
|
+
end
|
33
|
+
|
34
|
+
"expected '#{match}' to not be printed, but it was. full output:\n#@full_output"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'pty'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module BlueShell
|
5
|
+
class Runner
|
6
|
+
def initialize(*args)
|
7
|
+
@stdout, slave = PTY.open
|
8
|
+
system('stty raw', :in => slave)
|
9
|
+
read, @stdin = IO.pipe
|
10
|
+
|
11
|
+
@pid = spawn(*(args.push(:in => read, :out => slave, :err => slave)))
|
12
|
+
|
13
|
+
@expector = BufferedReaderExpector.new(@stdout, ENV['DEBUG_BACON'])
|
14
|
+
|
15
|
+
if block_given?
|
16
|
+
yield self
|
17
|
+
else
|
18
|
+
code = exit_code
|
19
|
+
raise Errors::NonZeroExitCodeError.new(code) unless code == 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
alias_method :run, :new
|
25
|
+
end
|
26
|
+
|
27
|
+
def expect(matcher, timeout = 30)
|
28
|
+
case matcher
|
29
|
+
when Hash
|
30
|
+
expect_branches(matcher, timeout)
|
31
|
+
else
|
32
|
+
@expector.expect(matcher, timeout)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_keys(text_to_send)
|
37
|
+
@stdin.puts(text_to_send)
|
38
|
+
end
|
39
|
+
|
40
|
+
def exit_code
|
41
|
+
return @code if @code
|
42
|
+
|
43
|
+
code = nil
|
44
|
+
Timeout.timeout(5) do
|
45
|
+
_, code = Process.waitpid2(@pid)
|
46
|
+
end
|
47
|
+
|
48
|
+
@code = numeric_exit_code(code)
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :wait_for_exit, :exit_code
|
52
|
+
|
53
|
+
def exited?
|
54
|
+
!running?
|
55
|
+
end
|
56
|
+
|
57
|
+
def success?
|
58
|
+
@code.zero?
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :successful?, :success?
|
62
|
+
|
63
|
+
def running?
|
64
|
+
!!Process.getpgid(@pid)
|
65
|
+
end
|
66
|
+
|
67
|
+
def output
|
68
|
+
@expector.output
|
69
|
+
end
|
70
|
+
|
71
|
+
def debug
|
72
|
+
@expector.debug
|
73
|
+
end
|
74
|
+
|
75
|
+
def debug=(x)
|
76
|
+
@expector.debug = x
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def expect_branches(branches, timeout)
|
82
|
+
branch_names = /#{branches.keys.collect { |k| Regexp.quote(k) }.join('|')}/
|
83
|
+
expected = @expector.expect(branch_names, timeout)
|
84
|
+
return unless expected
|
85
|
+
|
86
|
+
data = expected.first.match(/(#{branch_names})$/)
|
87
|
+
matched = data[1]
|
88
|
+
branches[matched].call
|
89
|
+
matched
|
90
|
+
end
|
91
|
+
|
92
|
+
def numeric_exit_code(status)
|
93
|
+
status.exitstatus
|
94
|
+
rescue NoMethodError
|
95
|
+
status
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BlueShell
|
4
|
+
module Matchers
|
5
|
+
describe ExitCodeMatcher, :ruby19 => true do
|
6
|
+
let(:expected_code) { 0 }
|
7
|
+
|
8
|
+
subject { ExitCodeMatcher.new(expected_code) }
|
9
|
+
|
10
|
+
describe "#matches?" do
|
11
|
+
context "with something that isn't a runner" do
|
12
|
+
it "raises an exception" do
|
13
|
+
expect {
|
14
|
+
subject.matches?("c'est ne pas une specker runner")
|
15
|
+
}.to raise_exception(Errors::InvalidInputError)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context "with a valid runner" do
|
20
|
+
context "and the command exited with the expected exit code" do
|
21
|
+
it "returns true" do
|
22
|
+
BlueShell::Runner.run("true") do |runner|
|
23
|
+
subject.matches?(runner).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "and the command exits with a different exit code" do
|
29
|
+
it "returns false" do
|
30
|
+
BlueShell::Runner.run("false") do |runner|
|
31
|
+
subject.matches?(runner).should be_false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "and the command runs for a while" do
|
37
|
+
it "waits for it to exit" do
|
38
|
+
BlueShell::Runner.run("sleep 0.5") do |runner|
|
39
|
+
subject.matches?(runner).should be_true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "failure messages" do
|
47
|
+
context "with a command that's exited" do
|
48
|
+
it "has a correct failure message" do
|
49
|
+
BlueShell::Runner.run("false") do |runner|
|
50
|
+
subject.matches?(runner)
|
51
|
+
runner.wait_for_exit
|
52
|
+
subject.failure_message.should == "expected process to exit with status 0, but it exited with status 1"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "has a correct negative failure message" do
|
57
|
+
BlueShell::Runner.run("false") do |runner|
|
58
|
+
subject.matches?(runner)
|
59
|
+
runner.wait_for_exit
|
60
|
+
subject.negative_failure_message.should == "expected process to not exit with status 0, but it did"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with a command that's still running" do
|
66
|
+
it "waits for it to exit" do
|
67
|
+
BlueShell::Runner.run("ruby -e 'sleep 1; exit 1'") do |runner|
|
68
|
+
subject.matches?(runner)
|
69
|
+
subject.failure_message.should == "expected process to exit with status 0, but it exited with status 1"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BlueShell
|
4
|
+
module Matchers
|
5
|
+
describe OutputMatcher, :ruby19 => true do
|
6
|
+
let(:expected_output) { "expected_output" }
|
7
|
+
let(:timeout) { 1 }
|
8
|
+
|
9
|
+
subject { OutputMatcher.new(expected_output, timeout) }
|
10
|
+
|
11
|
+
describe "#matches?" do
|
12
|
+
context "with something that isn't a runner" do
|
13
|
+
it "raises an exception" do
|
14
|
+
expect {
|
15
|
+
subject.matches?("c'est ne pas une specker runner")
|
16
|
+
}.to raise_exception(Errors::InvalidInputError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with a valid runner" do
|
21
|
+
context "when the expected output is in the process output" do
|
22
|
+
it "finds the expected output" do
|
23
|
+
BlueShell::Runner.run("echo -n expected_output") do |runner|
|
24
|
+
subject.matches?(runner).should be_true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "when the expected output is not in the process output" do
|
30
|
+
let(:runner) { Runner.new('echo -n not_what_we_were_expecting') }
|
31
|
+
|
32
|
+
it "does not find the expected output" do
|
33
|
+
BlueShell::Runner.run("echo -n not_what_we_were_expecting") do |runner|
|
34
|
+
subject.matches?(runner).should be_false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "failure messages" do
|
42
|
+
it "has a correct failure message" do
|
43
|
+
BlueShell::Runner.run("echo -n actual_output") do |runner|
|
44
|
+
subject.matches?(runner)
|
45
|
+
subject.failure_message.should == "expected 'expected_output' to be printed, but it wasn't. full output:\nactual_output"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "has a correct negative failure message" do
|
50
|
+
BlueShell::Runner.run("echo -n actual_output") do |runner|
|
51
|
+
subject.matches?(runner)
|
52
|
+
subject.negative_failure_message.should == "expected 'expected_output' to not be printed, but it was. full output:\nactual_output"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "when expecting branching output" do
|
57
|
+
let(:expected_output) { {
|
58
|
+
"expected_output" => proc {},
|
59
|
+
"other_expected_output" => proc {}
|
60
|
+
} }
|
61
|
+
|
62
|
+
it "has a correct failure message" do
|
63
|
+
BlueShell::Runner.run("echo -n actual_output") do |runner|
|
64
|
+
subject.matches?(runner)
|
65
|
+
subject.failure_message.should == "expected one of 'expected_output', 'other_expected_output' to be printed, but it wasn't. full output:\nactual_output"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "has a correct negative failure message" do
|
70
|
+
BlueShell::Runner.run("echo -n expected_output") do |runner|
|
71
|
+
subject.matches?(runner)
|
72
|
+
subject.negative_failure_message.should == "expected 'expected_output' to not be printed, but it was. full output:\nexpected_output"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BlueShell
|
4
|
+
describe Matchers do
|
5
|
+
include Matchers
|
6
|
+
|
7
|
+
describe "#say" do
|
8
|
+
it "returns an ExpectOutputMatcher" do
|
9
|
+
say("").should be_a(Matchers::OutputMatcher)
|
10
|
+
end
|
11
|
+
|
12
|
+
context "with an explicit timeout" do
|
13
|
+
it "returns an ExpectOutputMatcher" do
|
14
|
+
matcher = say("", 30)
|
15
|
+
matcher.should be_a(Matchers::OutputMatcher)
|
16
|
+
matcher.timeout.should == 30
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#have_exited_with" do
|
22
|
+
it "returns an ExitCodeMatcher" do
|
23
|
+
have_exited_with(1).should be_a(Matchers::ExitCodeMatcher)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "has synonyms" do
|
27
|
+
exit_with(1).should be_a(Matchers::ExitCodeMatcher)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/runner_spec.rb
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module BlueShell
|
5
|
+
describe Runner do
|
6
|
+
let(:timeout) { 1 }
|
7
|
+
|
8
|
+
describe "running a command" do
|
9
|
+
let(:file) do
|
10
|
+
file = Tempfile.new('blue-shell-runner')
|
11
|
+
sleep 1 # wait one second to make sure touching the file does something measurable
|
12
|
+
file
|
13
|
+
end
|
14
|
+
|
15
|
+
after { file.unlink }
|
16
|
+
|
17
|
+
context "with an invalid command" do
|
18
|
+
it "raises an exception" do
|
19
|
+
expect {
|
20
|
+
BlueShell::Runner.run("false")
|
21
|
+
}.to raise_error(Errors::NonZeroExitCodeError) { |error| error.exit_code.should == 1 }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with a valid command" do
|
26
|
+
it "runs a command" do
|
27
|
+
BlueShell::Runner.run("touch -a #{file.path}")
|
28
|
+
file.stat.atime.should > file.stat.mtime
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#success? and #successful?" do
|
34
|
+
context "when the command has a non-zero exit code" do
|
35
|
+
it "returns false" do
|
36
|
+
runner = BlueShell::Runner.run("false") { |runner|
|
37
|
+
runner.wait_for_exit
|
38
|
+
}
|
39
|
+
runner.should_not be_success
|
40
|
+
runner.should_not be_successful
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "when the command has a zero exit code" do
|
45
|
+
it "returns true" do
|
46
|
+
runner = BlueShell::Runner.run("true")
|
47
|
+
runner.should be_success
|
48
|
+
runner.should be_successful
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#expect" do
|
54
|
+
context "when the expected output shows up" do
|
55
|
+
it "returns a truthy value" do
|
56
|
+
BlueShell::Runner.run("echo -n foo") do |runner|
|
57
|
+
expect(runner.expect('foo')).to be_true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when the expected output never shows up" do
|
63
|
+
it "returns nil" do
|
64
|
+
BlueShell::Runner.run("echo the spanish inquisition") do |runner|
|
65
|
+
expect(runner.expect("something else", 0.5)).to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when the output eventually shows up" do
|
71
|
+
it "returns a truthy value" do
|
72
|
+
BlueShell::Runner.run("ruby #{asset("pause.rb")}") do |runner|
|
73
|
+
expect(runner.expect("finished")).to be_true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "backspace" do
|
79
|
+
it "respects the backspace character" do
|
80
|
+
BlueShell::Runner.run("ruby -e 'puts \"foo a\\bbar\"'") do |runner|
|
81
|
+
expect(runner.expect("foo bar")).to be_true
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not go beyond the beginning of the line" do
|
86
|
+
BlueShell::Runner.run("ruby -e 'print \"foo abc\nx\\b\\bd\"'") do |runner|
|
87
|
+
expect(runner.expect("foo abc\nd")).to be_true
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "does not go beyond the beginning of the string" do
|
92
|
+
BlueShell::Runner.run("ruby -e 'print \"f\\b\\bbar\"'") do |runner|
|
93
|
+
expect(runner.expect("bar")).to be_true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "leaves backspaced characters in the buffer until they're overwritten" do
|
98
|
+
BlueShell::Runner.run("ruby -e 'print \"foo abc\\b\\bd\"'") do |runner|
|
99
|
+
expect(runner.expect("foo adc")).to be_true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context "ansi escape sequences" do
|
105
|
+
it "filters ansi color sequences" do
|
106
|
+
BlueShell::Runner.run("ruby -e 'puts \"\\e[36mblue\\e[0m thing\"'") do |runner|
|
107
|
+
expect(runner.expect("blue thing")).to be_true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "expecting multiple branches" do
|
113
|
+
context "and one of them matches" do
|
114
|
+
it "can be passed a hash of values with callbacks, and returns the matched key" do
|
115
|
+
BlueShell::Runner.run("echo 1 3") do |runner|
|
116
|
+
branches = {
|
117
|
+
"1" => proc { 1 },
|
118
|
+
"2" => proc { 2 },
|
119
|
+
"3" => proc { 3 }
|
120
|
+
}
|
121
|
+
|
122
|
+
expect(runner.expect(branches)).to eq "1"
|
123
|
+
expect(runner.expect(branches)).to eq "3"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
it "calls the matched callback" do
|
128
|
+
callback = mock!
|
129
|
+
BlueShell::Runner.run("echo 1 3") do |runner|
|
130
|
+
branches = {
|
131
|
+
"1" => proc { callback }
|
132
|
+
}
|
133
|
+
runner.expect(branches)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "and none of them match" do
|
139
|
+
it "returns nil when none of the branches match" do
|
140
|
+
BlueShell::Runner.run("echo not_a_number") do |runner|
|
141
|
+
expect(runner.expect({"1" => proc { 1 }}, timeout)).to be_nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#output" do
|
149
|
+
it "makes the entire command output (so far) available" do
|
150
|
+
BlueShell::Runner.run("echo 0 1 2 3") do |runner|
|
151
|
+
runner.expect("1")
|
152
|
+
runner.expect("3")
|
153
|
+
expect(runner.output).to eq "0 1 2 3"
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe "#send_keys" do
|
160
|
+
it "sends input and expects more output afterward" do
|
161
|
+
BlueShell::Runner.run("ruby #{asset("input.rb")}") do |runner|
|
162
|
+
expect(runner.expect("started")).to be_true
|
163
|
+
runner.send_keys("foo")
|
164
|
+
expect(runner.expect("foo")).to be_true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "#exit_code" do
|
170
|
+
it "returns the exit code" do
|
171
|
+
BlueShell::Runner.run("ruby -e 'exit 42'") do |runner|
|
172
|
+
runner.wait_for_exit
|
173
|
+
expect(runner.exit_code).to eq(42)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "when the command is still running" do
|
178
|
+
it "waits for the command to exit" do
|
179
|
+
BlueShell::Runner.run("sleep 0.5") do |runner|
|
180
|
+
expect(runner.exit_code).to eq(0)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
context "#exited?" do
|
187
|
+
it "returns false if the command is still running" do
|
188
|
+
BlueShell::Runner.run("sleep 10") do |runner|
|
189
|
+
expect(runner.exited?).to eq false
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: blue-shell
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Pivotal Labs
|
9
|
+
- Cloud Foundry
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-04-09 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rake
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rr
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
description:
|
64
|
+
email:
|
65
|
+
- cfpi-frontend@googlegroups.com
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- LICENSE
|
71
|
+
- Rakefile
|
72
|
+
- lib/blue-shell/buffered_reader_expector.rb
|
73
|
+
- lib/blue-shell/errors.rb
|
74
|
+
- lib/blue-shell/matchers/exit_code_matcher.rb
|
75
|
+
- lib/blue-shell/matchers/output_matcher.rb
|
76
|
+
- lib/blue-shell/matchers.rb
|
77
|
+
- lib/blue-shell/runner.rb
|
78
|
+
- lib/blue-shell/version.rb
|
79
|
+
- lib/blue-shell.rb
|
80
|
+
- spec/assets/input.rb
|
81
|
+
- spec/assets/pause.rb
|
82
|
+
- spec/matchers/exit_code_matcher_spec.rb
|
83
|
+
- spec/matchers/output_matcher_spec.rb
|
84
|
+
- spec/matchers_spec.rb
|
85
|
+
- spec/runner_spec.rb
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/support/helpers.rb
|
88
|
+
homepage: http://github.com/pivotal/blue-shell
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ! '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 1.8.24
|
110
|
+
signing_key:
|
111
|
+
specification_version: 3
|
112
|
+
summary: Friendly command-line test runner and matchers for shell scripting in ruby
|
113
|
+
using rspec.
|
114
|
+
test_files:
|
115
|
+
- spec/assets/input.rb
|
116
|
+
- spec/assets/pause.rb
|
117
|
+
- spec/matchers/exit_code_matcher_spec.rb
|
118
|
+
- spec/matchers/output_matcher_spec.rb
|
119
|
+
- spec/matchers_spec.rb
|
120
|
+
- spec/runner_spec.rb
|
121
|
+
- spec/spec_helper.rb
|
122
|
+
- spec/support/helpers.rb
|