Module: Tap::Test::ShellTest

Defined in:
lib/tap/test/shell_test.rb,
lib/tap/test/shell_test/class_methods.rb,
lib/tap/test/shell_test/regexp_escape.rb

Overview

A module for testing shell scripts.

require 'test/unit'
class ShellTestSample < Test::Unit::TestCase
  include Tap::Test::ShellTest

  # these are the default sh_test options used 
  # in tests like test_sh_command_alias
  self.sh_test_options = {
    :cmd_pattern => '% inspect_argv',
    :cmd => 'ruby -e "puts ARGV.inspect"'
  }

  def test_echo
    assert_equal "goodnight moon", sh("echo goodnight moon").strip
  end

  def test_echo_using_sh_test
    sh_test %q{
echo goodnight moon
goodnight moon
}
  end

  def test_sh_command_alias
    sh_test("% inspect_env") do |output|
      assert output !~ /NEW_ENV_VAR/
    end

    sh_test("NEW_ENV_VAR=blue % inspect_env") do |output|
      assert output =~ /NEW_ENV_VAR=blue/
    end
  end
end

Defined Under Namespace

Modules: ClassMethods Classes: RegexpEscape

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

:nodoc:



44
45
46
47
# File 'lib/tap/test/shell_test.rb', line 44

def self.included(base) # :nodoc:
  super
  base.extend ShellTest::ClassMethods
end

Instance Method Details

#assert_alike(a, b, msg = nil) ⇒ Object



266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/tap/test/shell_test.rb', line 266

def assert_alike(a, b, msg=nil)
  if b =~ a
    assert true
  else
    flunk %Q{
#{msg}
================= expected output like ==================
#{whitespace_escape(a)}
======================== but was ========================
#{whitespace_escape(b)}
=========================================================
}
  end
end

#assert_output_equal(a, b, msg = nil) ⇒ Object



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/tap/test/shell_test.rb', line 250

def assert_output_equal(a, b, msg=nil)
  a = a[1..-1] if a[0] == ?\n
  if a == b
    assert true
  else
    flunk %Q{
#{msg}
==================== expected output ====================
#{whitespace_escape(a)}
======================== but was ========================
#{whitespace_escape(b)}
=========================================================
}
  end
end

#quiet?Boolean

Returns true if the ENV variable ‘QUIET’ is true. When quiet, ShellTest does not print any extra information to $stdout.

If ‘VERBOSE’ and ‘QUIET’ are both set, verbose wins.

Returns:

  • (Boolean)


67
68
69
70
71
72
# File 'lib/tap/test/shell_test.rb', line 67

def quiet?
  return false if verbose?
  
  quiet = ENV['QUIET']
  quiet && quiet =~ /^true$/i ? true : false
end

#set_env(env = {}, replace = false) ⇒ Object

Sets the specified ENV variables and returns the current env. If replace is true, current ENV variables are replaced; otherwise the new env variables are simply added to the existing set.



77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/tap/test/shell_test.rb', line 77

def set_env(env={}, replace=false)
  current_env = {}
  ENV.each_pair do |key, value|
    current_env[key] = value
  end
  
  ENV.clear if replace
  
  env.each_pair do |key, value|
    ENV[key] = value
  end if env
  
  current_env
end

#setupObject

Sets up the ShellTest module. Be sure to call super if you override setup in an including module.



51
52
53
54
# File 'lib/tap/test/shell_test.rb', line 51

def setup
  super
  @notify_method_name = true
end

#sh(cmd, options = {}) ⇒ Object

Executes the command using IO.popen and returns the stdout content.

Note

On Windows this method requires the win32-popen3 utility. If it is not available, it will have to be installed:

% gem install win32-open3


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/tap/test/shell_test.rb', line 117

def sh(cmd, options={})
  if @notify_method_name && !quiet?
    @notify_method_name = false
    puts
    puts method_name 
  end
  
  original_cmd = cmd
  if cmd_pattern = options[:cmd_pattern]
    cmd = cmd.sub(cmd_pattern, options[:cmd].to_s)
  end
  
  start = Time.now
  result = with_env(options[:env], options[:replace_env]) do
    IO.popen(cmd) do |io|
      yield(io) if block_given?
      io.read
    end
  end
  
  finish = Time.now
  elapsed = "%.3f" % [finish-start]
  puts "  (#{elapsed}s) #{verbose? ? cmd : original_cmd}" unless quiet?
  result
end

#sh_match(cmd, *regexps) {|result| ... } ⇒ Object

Yields:

  • (result)


231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/tap/test/shell_test.rb', line 231

def sh_match(cmd, *regexps)
  
  options = regexps.last.kind_of?(Hash) ? regexps.pop : {}
  options = sh_test_options.merge(options)
  result = sh(cmd, options)

  regexps.each do |regexp|
    assert_match regexp, result, cmd
  end
  yield(result) if block_given?
  result
end

#sh_test(cmd, options = {}) {|result| ... } ⇒ Object

Peforms a shell test. Shell tests execute the command and yield the $stdout result to the block for validation. The command is executed through sh, ie using IO.popen.

Options provided to sh_test are merged with the sh_test_options set for the class.

Command Aliases

The options allow specification of a command pattern that gets replaced with a command alias. Only the first instance of the command pattern is replaced. In addition, shell tests allow the expected result to be specified inline with the command. Used together, these allow multiple tests of a complex command to be specified easily:

opts = {
  :cmd_pattern => '% argv_inspect',
  :cmd => 'ruby -e "puts ARGV.inspect"'
}

sh_test %Q{
% argv_inspect goodnight moon
["goodnight", "moon"]
}, opts

sh_test %Q{
% argv_inspect hello world
["hello", "world"]
}, opts

Indents

To improve the readability of tests, sh_test will lstrip each line in the expected output to the same degree as the command line. So for instance these all pass:

sh_test %Q{
% argv_inspect hello world
["hello", "world"]
}, opts

sh_test %Q{
    % argv_inspect hello world
    ["hello", "world"]
}, opts

    sh_test %Q{
    % argv_inspect hello world
    ["hello", "world"]
    }, opts

Turn off indent stripping by specifying :indent => false.

ENV variables

Options may specify a hash of env variables that will be set in the subprocess.

sh_test %Q{
ruby -e "puts ENV['SAMPLE']"
value
}, :env => {'SAMPLE' => 'value'}

Note it is better to specify env variables in this way rather than through the command trick ‘VAR=value cmd …’, as that syntax does not work on Windows. As a point of interest, see gist.github.com/107363 for a demonstration of ENV variables being inherited by subprocesses.

Yields:

  • (result)


212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/tap/test/shell_test.rb', line 212

def sh_test(cmd, options={})
  options = sh_test_options.merge(options)
  
  if cmd =~ /\A\s*?\n?(\s*)(.*?\n)(.*)\z/m
    indent, cmd, expected = $1, $2, $3
    cmd.strip!
    
    if indent.length > 0 && options[:indents]
      expected.gsub!(/^\s{0,#{indent.length}}/, '')
    end
  end
  
  result = sh(cmd, options)
  
  assert_equal(expected, result, cmd) if expected
  yield(result) if block_given?
  result
end

#sh_test_optionsObject

Returns a hash of the default sh_test options. See ShellTest::ClassMethods#sh_test_options.



246
247
248
# File 'lib/tap/test/shell_test.rb', line 246

def sh_test_options
  self.class.sh_test_options
end

#verbose?Boolean

Returns true if the ENV variable ‘VERBOSE’ is true. When verbose, ShellTest prints the expanded commands of sh_test to $stdout.

Returns:

  • (Boolean)


58
59
60
61
# File 'lib/tap/test/shell_test.rb', line 58

def verbose?
  verbose = ENV['VERBOSE']
  verbose && verbose =~ /^true$/i ? true : false
end

#with_env(env = {}, replace = false) ⇒ Object

Sets the specified ENV variables for the duration of the block. If replace is true, current ENV variables are replaced; otherwise the new env variables are simply added to the existing set.

Returns the block return.



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/tap/test/shell_test.rb', line 97

def with_env(env={}, replace=false)
  current_env = nil
  begin
    current_env = set_env(env, replace)
    yield
  ensure
    if current_env
      set_env(current_env, true)
    end
  end
end