shell_test 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.rdoc CHANGED
@@ -1,3 +1,10 @@
1
+ == 0.4.0 2011/11/01
2
+
3
+ Renames prepare methods in FileMethods to setup_file and setup_dir, and
4
+ changes numerous class methods for setting up method dir cleanup.
5
+
6
+ * Parser now escapes special chars in inline prompts [issue #16]
7
+
1
8
  == 0.3.0 2011/10/23
2
9
 
3
10
  Rewrite and expansion of library to use PTY shell sessions rather than
data/README.rdoc CHANGED
@@ -16,20 +16,44 @@ ShellTest builds on modules that provide specific functionality. The modules
16
16
  may be used independently, but by including ShellTest you get them all:
17
17
 
18
18
  require 'shell_test/unit'
19
-
19
+
20
20
  class ShellTestExample < Test::Unit::TestCase
21
21
  include ShellTest
22
-
22
+
23
23
  def test_a_script
24
- script = prepare('script.sh') do |io|
25
- io.puts "echo goodnight $1"
26
- end
27
-
24
+ script = setup_file 'script.sh', %{
25
+ echo goodnight $1
26
+ }
27
+
28
28
  assert_script %{
29
29
  $ sh '#{script}' moon
30
30
  goodnight moon
31
31
  }, :exitstatus => 0
32
32
  end
33
+
34
+ def test_a_script_that_takes_input
35
+ script = setup_file 'script.sh', %{
36
+ stty -echo
37
+ while true; do
38
+ printf "Do you wish to continue? [y/n]: "
39
+ read answer
40
+ case $answer in
41
+ y ) printf "\nOk!\n"; break;;
42
+ n ) printf "\nToo bad.\n"; break;;
43
+ * ) printf "\nPlease answer y or n.\n";;
44
+ esac
45
+ done
46
+ stty echo
47
+ }
48
+
49
+ assert_script %{
50
+ $ sh '#{script}'
51
+ Do you wish to continue? [y/n]: {{hmmm}}
52
+ Please answer y or n.
53
+ Do you wish to continue? [y/n]: {{y}}
54
+ Ok!
55
+ }
56
+ end
33
57
  end
34
58
 
35
59
  ==== {ShellMethods}[link:classes/ShellTest/ShellMethods.html]
@@ -81,17 +105,13 @@ from the terminal.
81
105
  }
82
106
  end
83
107
 
84
- def test_scripts_that_take_input
85
- assert_script %{
86
- $ sudo echo 'sorry i cant do that dave'
87
- Password:{{notIt}}
88
- Sorry, try again.
89
- Password:{{mayBeThis}}
90
- Sorry, try again.
91
- Password:{{cr@pWhatIsIt}}
92
- Sorry, try again.
93
- sudo: 3 incorrect password attempts
94
- }, :max_run_time => 10
108
+ def test_scripts_where_alternate_prompts_are_needed
109
+ with_env('PS1' => '% ') do
110
+ assert_script %{
111
+ % echo '$ $ $'
112
+ $ $ $
113
+ }
114
+ end
95
115
  end
96
116
  end
97
117
 
@@ -99,7 +119,8 @@ from the terminal.
99
119
 
100
120
  Sets up a temporary, test-specific directory for working with files. This
101
121
  approach is better in most cases than using Tempfile because you can flag the
102
- directory to be saved on a failure (using ENV['KEEP_OUTPUTS']='true').
122
+ directory to be saved on a failure (using ENV['KEEP_OUTPUTS']='true') and
123
+ immediately know where to look for your files.
103
124
 
104
125
  By default the directory is guessed based off of the test file and test
105
126
  method. If this example were located in the 'test/file_methods_example.rb'
@@ -107,12 +128,12 @@ file, then the directory for the test case would be
107
128
  'test/file_methods_example/test_preparation_of_a_test_specific_file'.
108
129
 
109
130
  require 'shell_test/unit'
110
-
131
+
111
132
  class FileMethodsExample < Test::Unit::TestCase
112
133
  include ShellTest::FileMethods
113
134
 
114
- def test_preparation_of_a_test_specific_file
115
- path = prepare('dir/file.txt') {|io| io << 'content' }
135
+ def test_setup_of_a_test_specific_file
136
+ path = setup_file('dir/file.txt') {|io| io << 'content' }
116
137
  assert_equal "content", File.read(path)
117
138
  end
118
139
  end
@@ -6,29 +6,47 @@ module ShellTest
6
6
  module ClassMethods
7
7
  attr_accessor :class_dir
8
8
 
9
- attr_reader :cleanup_method_registry
10
-
11
- def cleanup_methods
12
- @cleanup_methods ||= begin
13
- cleanup_methods = {}
9
+ # A registry tracking paths_to_cleanup for the current class.
10
+ attr_reader :paths_to_cleanup_registry
11
+
12
+ # A hash of (method_name, [relative_path]) pairs identifying which
13
+ # relative paths on each method have been marked on this class or
14
+ # inherited from ancestors. Entries in paths_to_cleanup should not be
15
+ # edited directly. Instead use:
16
+ #
17
+ # cleanup : turn on cleanup for methods
18
+ # do_not_cleanup : turn off cleanup for methods
19
+ # default_paths_to_cleanup : set the default paths to cleanup
20
+ #
21
+ # Or if you need very precise editing, use (with the same semantics as
22
+ # {define/remove/undef}_method):
23
+ #
24
+ # define_paths_to_cleanup
25
+ # remove_paths_to_cleanup
26
+ # undef_paths_to_cleanup
27
+ #
28
+ def paths_to_cleanup
29
+ @paths_to_cleanup ||= begin
30
+ paths_to_cleanup = {}
14
31
 
15
32
  ancestors.reverse.each do |ancestor|
16
33
  next unless ancestor.kind_of?(ClassMethods)
17
- ancestor.cleanup_method_registry.each_pair do |key, value|
34
+ ancestor.paths_to_cleanup_registry.each_pair do |key, value|
18
35
  if value.nil?
19
- cleanup_methods.delete(key)
36
+ paths_to_cleanup.delete(key)
20
37
  else
21
- cleanup_methods[key] = value
38
+ paths_to_cleanup[key] = value
22
39
  end
23
40
  end
24
41
  end
25
42
 
26
- cleanup_methods
43
+ paths_to_cleanup
27
44
  end
28
45
  end
29
46
 
30
- def reset_cleanup_methods
31
- @cleanup_methods = nil
47
+ # Resets paths_to_cleanup such that it will be recalculated.
48
+ def reset_paths_to_cleanup
49
+ @paths_to_cleanup = nil
32
50
  end
33
51
 
34
52
  protected
@@ -48,13 +66,13 @@ module ShellTest
48
66
  base.class_dir = Dir.tmpdir
49
67
  end
50
68
 
51
- base.reset_cleanup_methods
52
- unless base.instance_variable_defined?(:@cleanup_method_registry)
53
- base.instance_variable_set(:@cleanup_method_registry, {})
69
+ base.reset_paths_to_cleanup
70
+ unless base.instance_variable_defined?(:@paths_to_cleanup_registry)
71
+ base.instance_variable_set(:@paths_to_cleanup_registry, {})
54
72
  end
55
73
 
56
- unless base.instance_variable_defined?(:@cleanup_paths)
57
- base.instance_variable_set(:@cleanup_paths, ['.'])
74
+ unless base.instance_variable_defined?(:@default_paths_to_cleanup)
75
+ base.instance_variable_set(:@default_paths_to_cleanup, ['.'])
58
76
  end
59
77
 
60
78
  unless base.instance_variable_defined?(:@cleanup)
@@ -67,47 +85,65 @@ module ShellTest
67
85
  super
68
86
  end
69
87
 
70
- def define_method_cleanup(method_name, dirs)
71
- reset_cleanup_methods
72
- cleanup_method_registry[method_name.to_sym] = dirs
88
+ # Define the paths_to_cleanup for the specified method. The settings
89
+ # are inherited, but can be overridden in subclasses.
90
+ def define_paths_to_cleanup(method_name, relative_paths)
91
+ reset_paths_to_cleanup
92
+ paths_to_cleanup_registry[method_name.to_sym] = relative_paths
73
93
  end
74
94
 
75
- def remove_method_cleanup(method_name)
76
- reset_cleanup_methods
77
- cleanup_method_registry.delete(method_name.to_sym)
95
+ # Remove the paths_to_cleanup for the method as defined on self. The
96
+ # paths_to_cleanup inherited from ancestors will still be in effect.
97
+ def remove_paths_to_cleanup(method_name)
98
+ reset_paths_to_cleanup
99
+ paths_to_cleanup_registry.delete(method_name.to_sym)
78
100
  end
79
101
 
80
- def undef_method_cleanup(method_name)
81
- reset_cleanup_methods
82
- cleanup_method_registry[method_name.to_sym] = nil
102
+ # Undefines the paths_to_cleanup for the method, preventing inheritance
103
+ # from ancestors.
104
+ def undef_paths_to_cleanup(method_name)
105
+ reset_paths_to_cleanup
106
+ paths_to_cleanup_registry[method_name.to_sym] = nil
83
107
  end
84
108
 
85
- def cleanup_paths(*dirs)
86
- @cleanup_paths = dirs
109
+ # Sets the default paths_to_cleanup for subsequent methods.
110
+ def default_paths_to_cleanup(*relative_paths)
111
+ @default_paths_to_cleanup = relative_paths
87
112
  end
88
113
 
114
+ # Mark the methods for cleanup using the default_paths_to_cleanup. Call
115
+ # without method names to mark all subsequent methods for cleanup.
89
116
  def cleanup(*method_names)
90
117
  if method_names.empty?
91
118
  @cleanup = true
92
119
  else
93
120
  method_names.each do |method_name|
94
- define_method_cleanup method_name, @cleanup_paths
121
+ define_paths_to_cleanup method_name, @default_paths_to_cleanup
95
122
  end
96
123
  end
97
124
  end
98
125
 
99
- def no_cleanup(*method_names)
126
+ # Prevent cleanup for the methods. Call without method names to prevent
127
+ # cleanup for subsequent methods.
128
+ def do_not_cleanup(*method_names)
100
129
  if method_names.empty?
101
130
  @cleanup = false
102
131
  else
103
132
  method_names.each do |method_name|
104
- undef_method_cleanup method_name
133
+ undef_paths_to_cleanup method_name
105
134
  end
106
135
  end
107
136
  end
108
137
 
138
+ # Returns true if the method should be marked for cleanup when added.
139
+ def mark_for_cleanup?(method_name)
140
+ @cleanup && !paths_to_cleanup_registry.has_key?(method_name.to_sym) && method_name.to_s.index("test_") == 0
141
+ end
142
+
143
+ # Overridden to ensure methods marked for cleanup are cleaned up.
109
144
  def method_added(sym)
110
- if @cleanup && !cleanup_method_registry.has_key?(sym.to_sym) && sym.to_s[0, 5] == "test_"
145
+ super
146
+ if mark_for_cleanup?(sym)
111
147
  cleanup sym
112
148
  end
113
149
  end
@@ -208,7 +244,7 @@ module ShellTest
208
244
  end
209
245
 
210
246
  # Creates a directory under method_dir.
211
- def prepare_dir(relative_path)
247
+ def setup_dir(relative_path)
212
248
  target_dir = path(relative_path)
213
249
  unless File.directory?(target_dir)
214
250
  FileUtils.mkdir_p(target_dir)
@@ -216,8 +252,10 @@ module ShellTest
216
252
  target_dir
217
253
  end
218
254
 
219
- # Same as prepare but does not outdent content.
220
- def _prepare(relative_path, content=nil, &block)
255
+ alias prepare_dir setup_dir
256
+
257
+ # Same as setup_file but does not outdent content.
258
+ def _setup_file(relative_path, content=nil, &block)
221
259
  target = path(relative_path)
222
260
 
223
261
  if File.exists?(target)
@@ -241,16 +279,16 @@ module ShellTest
241
279
  # Content provided as a string is outdented (see StringMethods#outdent),
242
280
  # so this syntax is possible:
243
281
  #
244
- # path = prepare 'file', %{
282
+ # path = setup_file 'file', %{
245
283
  # line one
246
284
  # line two
247
285
  # }
248
286
  # File.read(path) # => "line one\nline two\n"
249
287
  #
250
288
  # Returns the absolute path to the new file.
251
- def prepare(relative_path, content=nil, &block)
289
+ def setup_file(relative_path, content=nil, &block)
252
290
  content = outdent(content) if content
253
- _prepare(relative_path, content, &block)
291
+ _setup_file(relative_path, content, &block)
254
292
  end
255
293
 
256
294
  # Returns the content of the file under method_dir, if it exists.
@@ -272,15 +310,10 @@ module ShellTest
272
310
  FileUtils.rm_r(full_path) if File.exists?(full_path)
273
311
  end
274
312
 
275
- # Shortcut to access the class.cleanup_methods.
276
- def cleanup_methods
277
- self.class.cleanup_methods
278
- end
279
-
280
- # Recursively removes paths specified for cleanup in cleanup_methods.
313
+ # Recursively removes paths specified for cleanup by paths_to_cleanup.
281
314
  def cleanup
282
- if cleanup_paths = cleanup_methods[method_name.to_sym]
283
- cleanup_paths.each {|relative_path| remove(relative_path) }
315
+ if paths = self.class.paths_to_cleanup[method_name.to_sym]
316
+ paths.each {|path| remove(path) }
284
317
  end
285
318
  end
286
319
  end
@@ -20,32 +20,33 @@ module ShellTest
20
20
  # timer using `timer.timeout=` and retrieved via `timer.timeout`.
21
21
  attr_reader :timer
22
22
 
23
- # A hash of [name, regexp] pairs mapping logical names to prompts.
24
- attr_reader :prompts
25
-
26
- def initialize(master, slave, timer, prompts={})
23
+ def initialize(master, slave, timer)
27
24
  @master = master
28
25
  @slave = slave
29
26
  @timer = timer
30
- @prompts = prompts
31
27
  end
32
28
 
33
- def on(prompt, input, timeout=nil)
34
- output = expect(prompt, timeout)
29
+ def on(regexp, input, timeout=nil)
30
+ output = expect(regexp, timeout)
35
31
  write(input)
36
32
  output
37
33
  end
38
34
 
39
- # Reads from the slave until the prompt (a regexp) is matched and
40
- # returns the resulting string. If a nil prompt is given then expect
41
- # reads until the slave eof.
35
+ # Reads from the slave until the regexp is matched and returns the
36
+ # resulting string. If regexp is a String, then it is converted into a
37
+ # regexp assuming it's a literal prompt (ie /^regexp\z/ - where the
38
+ # regexp string is Regexp escaped). If regexp is nil then expect reads
39
+ # until the slave eof.
42
40
  #
43
41
  # A timeout may be given. If the slave doesn't produce the expected
44
42
  # string within the timeout then expect raises a ReadError. A ReadError
45
- # will be also be raised if the slave eof is reached before the prompt
43
+ # will be also be raised if the slave eof is reached before the regexp
46
44
  # matches.
47
- def expect(prompt, timeout=nil)
48
- regexp = prompts[prompt] || prompt
45
+ def expect(regexp, timeout=nil)
46
+ if regexp.kind_of?(String)
47
+ regexp = /#{Regexp.escape(regexp)}\z/
48
+ end
49
+
49
50
  timer.timeout = timeout
50
51
 
51
52
  buffer = ''
@@ -66,10 +67,7 @@ module ShellTest
66
67
  begin
67
68
  c = slave.read(1)
68
69
  rescue Errno::EIO
69
- # On some linux (ex ubuntu) read can return an eof or fail with
70
- # an EIO error when a terminal disconnect occurs and an EIO
71
- # condition occurs - the exact behavior is unspecified but the
72
- # meaning is the same... no more data is available, so break.
70
+ # see notes in Utils#spawn
73
71
  c = nil
74
72
  end
75
73
 
@@ -1,4 +1,3 @@
1
- require 'shell_test/env_methods'
2
1
  require 'shell_test/shell_methods/agent'
3
2
  require 'shell_test/shell_methods/utils'
4
3
  require 'strscan'
@@ -8,24 +7,15 @@ module ShellTest
8
7
 
9
8
  # Session is an engine for running shell sessions.
10
9
  class Session
11
- include EnvMethods
12
10
  include Utils
13
11
 
14
12
  DEFAULT_SHELL = '/bin/sh'
15
- DEFAULT_PS1 = '$ '
16
- DEFAULT_PS2 = '> '
17
13
  DEFAULT_STTY = '-echo -onlcr'
18
14
  DEFAULT_MAX_RUN_TIME = 1
19
15
 
20
16
  # The session shell
21
17
  attr_reader :shell
22
18
 
23
- # The shell PS1
24
- attr_reader :ps1
25
-
26
- # The shell PS2
27
- attr_reader :ps2
28
-
29
19
  # Aguments string passed stty on run
30
20
  attr_reader :stty
31
21
 
@@ -47,18 +37,22 @@ module ShellTest
47
37
 
48
38
  def initialize(options={})
49
39
  @shell = options[:shell] || DEFAULT_SHELL
50
- @ps1 = options[:ps1] || DEFAULT_PS1
51
- @ps2 = options[:ps2] || DEFAULT_PS2
52
40
  @stty = options[:stty] || DEFAULT_STTY
53
41
  @timer = options[:timer] || Timer.new
54
42
  @max_run_time = options[:max_run_time] || DEFAULT_MAX_RUN_TIME
55
43
  @steps = [[nil, nil, nil, nil]]
56
44
  @log = []
57
45
  @status = nil
58
- @prompts = {
59
- :ps1 => /#{Regexp.escape(ps1)}/,
60
- :ps2 => /#{Regexp.escape(ps2)}/
61
- }
46
+ end
47
+
48
+ # The shell PS1, as configured in ENV.
49
+ def ps1
50
+ ENV['PS1']
51
+ end
52
+
53
+ # The shell PS2, as configured in ENV.
54
+ def ps2
55
+ ENV['PS2']
62
56
  end
63
57
 
64
58
  # Define a step. At each step:
@@ -99,11 +93,11 @@ module ShellTest
99
93
 
100
94
  def split(str)
101
95
  scanner = StringScanner.new(str)
102
- scanner.scan_until(/(#{@prompts[:ps1]})/)
96
+ scanner.scan_until(/(#{Regexp.escape(ps1)})/)
103
97
  scanner.pos -= scanner[1].to_s.length
104
98
 
105
99
  args = []
106
- while output = scanner.scan_until(/(#{@prompts[:ps1]}|#{@prompts[:ps2]}|\{\{(.*?)\}\})/)
100
+ while output = scanner.scan_until(/(#{Regexp.escape(ps1)}|#{Regexp.escape(ps2)}|\{\{(.*?)\}\})/)
107
101
  match = scanner[1]
108
102
  input = scanner[2] ? "#{scanner[2]}\n" : scanner.scan_until(/\n/)
109
103
 
@@ -115,15 +109,15 @@ module ShellTest
115
109
 
116
110
  case match
117
111
  when ps1
118
- prompt = :ps1
112
+ prompt = match
119
113
  if max_run_time == -1
120
114
  max_run_time = nil
121
115
  end
122
116
  when ps2
123
- prompt = :ps2
117
+ prompt = match
124
118
  else
125
119
  output = output.chomp(match)
126
- prompt = /^#{output.split("\n").last}\z/
120
+ prompt = output.split("\n").last
127
121
  end
128
122
 
129
123
  args << output
@@ -159,7 +153,7 @@ module ShellTest
159
153
  args.pop
160
154
  else
161
155
  args.last << ps1
162
- args.concat [:ps1, "exit\n", nil, nil]
156
+ args.concat [ps1, "exit\n", nil, nil]
163
157
  end
164
158
 
165
159
  while !args.empty?
@@ -178,53 +172,42 @@ module ShellTest
178
172
  # Spawns a PTY shell session and yields an Agent to the block. The
179
173
  # session is logged to log and the final exit status set into status
180
174
  # (any previous values are overwritten).
181
- #
182
- # ==== ENV variables
183
- #
184
- # PS1 and PS2 are set into ENV for the duration of the block and so in
185
- # most cases the shell inherits those values. Keep in mind, however,
186
- # that the shell config scripts can set these variables and on some
187
- # distributions (ex SLES 10) the config script do not respect prior
188
- # values.
189
- #
190
175
  def spawn
191
- with_env('PS1' => ps1, 'PS2' => ps2) do
192
- @log = []
193
- @status = super(shell) do |master, slave|
194
- agent = Agent.new(master, slave, timer, @prompts)
195
- timer.start(max_run_time)
196
-
197
- if stty
198
- # It would be lovely to work this into steps somehow, or to set
199
- # the stty externally like:
200
- #
201
- # system("stty #{stty} < '#{master.path}'")
202
- #
203
- # Unfortunately the former complicates result and the latter
204
- # doesn't work. In tests the stty settings DO get set but they
205
- # don't refresh in the pty.
206
- log << agent.on(:ps1, "stty #{stty}\n")
207
- log << agent.on(:ps1, "echo $?\n")
208
- log << agent.on(:ps1, "\n")
209
-
210
- unless log.last == "0\n#{ps1}"
211
- raise "stty failure\n#{summary}"
212
- end
213
-
214
- log.clear
176
+ @log = []
177
+ @status = super(shell) do |master, slave|
178
+ agent = Agent.new(master, slave, timer)
179
+ timer.start(max_run_time)
180
+
181
+ if stty
182
+ # It would be lovely to work this into steps somehow, or to set
183
+ # the stty externally like:
184
+ #
185
+ # system("stty #{stty} < '#{master.path}'")
186
+ #
187
+ # Unfortunately the former complicates result and the latter
188
+ # doesn't work. In tests the stty settings DO get set but they
189
+ # don't refresh in the pty.
190
+ log << agent.on(ps1, "stty #{stty}\n")
191
+ log << agent.on(ps1, "echo $?\n")
192
+ log << agent.on(ps1, "\n")
193
+
194
+ unless log.last == "0\n#{ps1}"
195
+ raise "stty failure\n#{summary}"
215
196
  end
216
197
 
217
- begin
218
- yield agent
219
- rescue Agent::ReadError
220
- log << $!.buffer
221
- $!.message << "\n#{summary}"
222
- raise
223
- end
198
+ log.clear
199
+ end
224
200
 
225
- timer.stop
226
- agent.close
201
+ begin
202
+ yield agent
203
+ rescue Agent::ReadError
204
+ log << $!.buffer
205
+ $!.message << "\n#{summary}"
206
+ raise
227
207
  end
208
+
209
+ timer.stop
210
+ agent.close
228
211
  end
229
212
  self
230
213
  end
@@ -14,40 +14,74 @@ module ShellTest
14
14
  # process and raise a PTY::ChildExited error in some cases. As a result
15
15
  # manual calls to Process.wait (which would set $?) cause a race
16
16
  # condition. Rely on the output of spawn instead.
17
- def spawn(cmd)
17
+ def spawn(cmd, log=[])
18
+ # The race condition described above actually applies to both kill and
19
+ # wait which raise Errno::ESRCH or Errno::ECHILD if they lose the
20
+ # race. This code is designed to capture those errors if they occur
21
+ # and then give the cleanup thread a chance to take over; eventually
22
+ # it will raise a ChildExited error. This is a sketchy use of
23
+ # exceptions for flow control but there is little option - a
24
+ # consequence of PTY using threads with side effects.
18
25
  PTY.spawn(cmd) do |slave, master, pid|
19
26
  begin
20
27
  yield(master, slave)
21
- Process.wait(pid)
28
+
29
+ begin
30
+ Process.wait(pid)
31
+ rescue Errno::ECHILD
32
+ Thread.pass
33
+ raise
34
+ end
22
35
 
23
36
  rescue PTY::ChildExited
24
- # handle a ChildExited error on 1.8.6 and 1.8.7 as a 'normal' exit
25
- # route. 1.9.2 does not exit this way.
37
+ # This is the 'normal' exit route on 1.8.6 and 1.8.7.
26
38
  return $!.status
27
39
 
28
- rescue Exception
29
- # cleanup the pty on error
30
- Process.kill(9, pid)
40
+ rescue Exception => error
41
+ begin
31
42
 
32
- # clearing the slave allows the wait to complete faster
33
- while IO.select([slave],nil,nil,0.1)
43
+ # Manually cleanup the pid on error. This code no longer cares
44
+ # what exactly happens to $? - the point is to make sure the
45
+ # child doesn't become a zombie and then re-raise the error.
34
46
  begin
35
- break unless slave.read(1)
36
- rescue Errno::EIO
37
- break
47
+ Process.kill(9, pid)
48
+ rescue Errno::ESRCH
49
+ Thread.pass
50
+ raise
38
51
  end
39
- end
40
52
 
41
- # any wait can cause a ChildExited error so account for that here
42
- # - the $? is indeterminate in this case prior to 1.9.2
43
- Process.wait(pid) rescue PTY::ChildExited
53
+ # Clearing the slave allows quicker exits on OS X.
54
+ while IO.select([slave],nil,nil,0.1)
55
+ begin
56
+ break unless slave.read(1)
57
+ rescue Errno::EIO
58
+ # On some linux (ex ubuntu) read can return an eof or fail with
59
+ # an EIO error when a terminal disconnect occurs and an EIO
60
+ # condition occurs - the exact behavior is unspecified but the
61
+ # meaning is the same... no more data is available, so break.
62
+ break
63
+ end
64
+ end
44
65
 
45
- raise
66
+ begin
67
+ Process.wait(pid)
68
+ rescue Errno::ECHILD
69
+ Thread.pass
70
+ raise
71
+ end
72
+
73
+ rescue PTY::ChildExited
74
+ # The cleanup thread could finish at any point in the rescue
75
+ # handling so account for that here.
76
+ ensure
77
+ raise error
78
+ end
46
79
  end
47
80
  end
48
81
 
49
82
  $?
50
83
  end
84
+
51
85
  end
52
86
  end
53
87
  end
@@ -1,3 +1,4 @@
1
+ require 'shell_test/env_methods'
1
2
  require 'shell_test/regexp_escape'
2
3
  require 'shell_test/string_methods'
3
4
  require 'shell_test/shell_methods/session'
@@ -7,6 +8,18 @@ module ShellTest
7
8
  include StringMethods
8
9
  include EnvMethods
9
10
 
11
+ attr_reader :original_env
12
+
13
+ def setup
14
+ super
15
+ @original_env = set_env('PS1' => '$ ', 'PS2' => '> ')
16
+ end
17
+
18
+ def teardown
19
+ set_env(@original_env)
20
+ super
21
+ end
22
+
10
23
  def pty(script, options={}, &block)
11
24
  _pty outdent(script), options, &block
12
25
  end
@@ -1,3 +1,3 @@
1
1
  module ShellTest
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shell_test
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
8
+ - 4
9
9
  - 0
10
- version: 0.3.0
10
+ version: 0.4.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Simon Chiang
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-23 00:00:00 -06:00
18
+ date: 2011-11-01 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies: []
21
21
 
@@ -48,7 +48,7 @@ files:
48
48
  - README.rdoc
49
49
  - MIT-LICENSE
50
50
  has_rdoc: true
51
- homepage: ""
51
+ homepage: http://github.com/thinkerbot/shell_test
52
52
  licenses:
53
53
  - MIT
54
54
  post_install_message: