module CmdLineTest module ClassMethods attr_accessor :cli_exit_status, :cli_output, :cli_error, :cli_error_stack, :cli_block def run_command_line_as(&block) self.cli_block = block end end #ClassMethods module InstanceMethods def run_with_options(options) with_ARGV_set_to(options) {execute} end private def execute self.class.cli_block.call end def with_ARGV_set_to(options) ignore_assignment_to_const_warning { Object.const_set(:ARGV, options) } yield end def ignore_assignment_to_const_warning warnings_option = $-w $-w = nil begin yield ensure $-w = warnings_option end end end #InstanceMethods module Macros def run_with_options(options, name = nil, &block) raise RuntimeError, "Command is not defined: 'run_command_line_as()' must be called first." unless self.cli_block test_name = name || "after running with options '#{options.join(', ')}'" generate_test test_name do with_ARGV_set_to options do begin self.class.cli_exit_status = 0 self.class.cli_error = "" self.class.cli_error_stack = "" self.class.cli_output = "" $test_class = self.class #It used to be $stdout = StringIO.new(...), but it does not work well with rdebug class <<$stdout alias_method :original_write, :write def write(*s) #original_write(s) $test_class.cli_output << s.to_s if $test_class.cli_output end end execute rescue SystemExit => exit_error self.class.cli_exit_status = exit_error.status rescue Exception => error self.class.cli_error_stack = error.backtrace.join("\n") self.class.cli_error = error.message ensure class <<$stdout remove_method :write, :original_write end end instance_eval(&block) end end end private def generate_test(name, &block) if defined? Shoulda should(name, &block) else test_name = ("test_".concat name).strip.to_sym self.send(:define_method, test_name, &block) end end end #Macros module Assertions def assert_successful_run expect_message = "Should run successfully, but" assert_equal 0, self.class.cli_exit_status, "#{expect_message} exit status is #{self.class.cli_exit_status}" assert self.class.cli_error.empty?, "#{expect_message} exception '#{self.class.cli_error}' has been thrown. Exception stack:\n" + self.class.cli_error_stack end def assert_failed_run flunk "Expects either thrown error or exit status not 0." if 0 == self.class.cli_exit_status && self.class.cli_error.empty? end def assert_out_contains regex assert_match regex, self.class.cli_output end end #Assertions end module Test module Unit class TestCase extend CmdLineTest::ClassMethods include CmdLineTest::InstanceMethods extend CmdLineTest::Macros include CmdLineTest::Assertions end end end