9

I am testing some scripts that interface with system commands. Their logic depends on the return code of the system commands, i.e. the value of $?. So, as a simplified example, the script might say:

def foo(command)
  output=`#{command}`
  if $?==0
    'succeeded'
  else
    'failed'
  end
end

In order to be able to test these methods properly, I would like to be able to stub out the Kernel backquote call, and set $? to an arbitrary value, to see if I get appropriate behavior from the logic in the method after the backquote call. $? is a read-only variable, so the following doesn't work:

$? = some_number

I can do some simple stuff: for example, set $? to zero or non-zero. For instance, will set $? to either 0 or 35212 (on my system, anyway), depending on the value of $?:

def fail_or_succeed(success)
  if success
    `echo foo`
  else
    `a-non-existent-command 2>&1`
  end
end

What I'd really like to be able to do is to set $? to a specific value (e.g. 3, or 122), not just zero or an arbitrary non-zero. I can't figure out a way to do this. (In case it matters, I'm testing using Test::Unit and Mocha.)

Vega
  • 27,856
  • 27
  • 95
  • 103
rleber
  • 573
  • 3
  • 9

3 Answers3

11

EDIT: Using Dennis Williamson's suggestion:

command = "(exit 21)"

and use if $?.exitstatus == 0 instead of if $? == 0

Aleksandr Levchuk
  • 3,751
  • 4
  • 35
  • 47
  • Aha! Thanks, that works. I had tried your idea, but had missed the .exitstatus part. I was using .to_i (in effect), which returns (in your example) 5376, not 21. – rleber Jan 04 '11 at 00:06
  • @rleber: It could be as simple as `command = '(exit 21)'` (you can omit the "bash -c" if you wrap it in parentheses). – Dennis Williamson Jan 04 '11 at 01:50
  • @Dennis, Nice - it's simpler and more reliable too (when bash is not in `$PATH`). – Aleksandr Levchuk Jan 04 '11 at 03:39
  • Is there any other way that is more direct, and stays inside the Ruby process? – Pysis Nov 16 '16 at 22:03
  • Tried something like `$? = Process::Status.new.expects(:exitstatus).returns(0).once`, but when trying this in a REPL (pry), I see there is the error `NameError: $? is a read-only variable`, so I guess I have to use another command like the answer then.. – Pysis Nov 17 '16 at 15:54
  • Also perused [this page](http://gofreerange.com/mocha/docs/Mocha/Expectation.html) to find a method that would allow me to add a block of code to run so it could set the exit status, and basically fill the `$?` global variable with a `Process::Status` object, since it was read-only, and the main code under test would not break, but I did not find anything I could use to do that :(. – Pysis Nov 17 '16 at 22:13
1
module Stubbed
  def `(*args)
    super( "mycommand that returns the error code I want" )
  end
end

Include this into your object when needed?

Phrogz
  • 296,393
  • 112
  • 651
  • 745
0

You could launch a subprocess from the tests that would just exit with whatever code you need. This will set the $?.exitstatus code for you, which will be available from the production code

For a production code like this:

def execute_something
  `my_command --parameter`
  $?.exitstatus == 0
end

You could mock the $?.exitstatus:

it 'should return false' do
  fork { exit 1 } # Change to whatever code you need
  Process.wait

  expect(@sut).to receive(:`).with('my_command --parameter')
  expect(@sut.execute_something).to be == false
end
Alex Salom
  • 3,074
  • 4
  • 22
  • 33