#! /usr/local/bin/ruby18
# Copyright (c) 2006-2007 rubikitch <rubikitch@ruby-lang.org>
#
# Use and distribution subject to the terms of the Ruby license.

USAGE = <<'XXX'
Usage: rbtest SCRIPT [-S RUBY_INTERPRETER] [Test::Unit OPTIONS]
Usage: rbtest [-h] [--help] [--example]

I am rbtest, embedded Test::Unit executor for one-file scripts.
Splitting a small script into many files (executables, libraries and tests) is cumbersome.
And it is handy to put unit tests near implementations like D language, which has
built-in unittest keyword.

Embedded Test::Unit is simpler than vanilla Test::Unit.
You do not have to define a Test::Unit::TestCase subclass,
it is automagically defined and executed by me.
Embedded Test::Unit uses =begin/=end comment blocks.

"=begin TEST_METHOD_NAME" blocks define test methods, eg. "=begin test_foo".
"=begin rbtest" blocks define utility methods and setup/teardown methods.

Of course, you MUST use "if __FILE__ ==$0" idiom to split executable and class/method/function.

I am also an real-life example of rbtest usage.
Issue:
  rbtest --example
to show me.


options:
  -h, --help           Print usage.
  -S RUBY_INTERPRETER  Use Ruby interpreter RUBY_INTERPRETER.
  --example            Print this file.
  --output             Print internally-generated test script (for debug). 
XXX


def first_test(script_filename)
  "require 'test/unit';" +
  "load '#{script_filename}';" +
  "class TestByRbtest < Test::Unit::TestCase;"
end

=begin rbtest
def setup
end

def unindent(s)
  s.lines.map{|x| x[1..-1]}.join
end
=end

=begin test_script_to_test_script
# indent is needed to avoid syntax error.
script = <<XXX
 #!/usr/bin/env ruby
 =begin test0
 assert_equal(10, f(1))
 =end
 def f(x) x*10 end
 =begin
 other block is skipped
 =end
 =begin test1
 assert_equal(100, f(10))
 =end
 puts f(99) if __FILE__==$0
XXX

test_script = <<'XXX'
 #!/usr/bin/env ruby
 require 'test/unit';load 'f.rb';class TestByRbtest < Test::Unit::TestCase;def test0
 assert_equal(10, f(1))
 end
 
 
 
 
 def test1
 assert_equal(100, f(10))
 end
 
 end
XXX
assert_equal unindent(test_script), script_to_test_script(unindent(script), "f.rb")

script = <<XXX
 =begin rbtest
 def setup() end
 =end
 def f(x) x*10 end
 =begin test1
 assert_equal(100, f(10))
 =end
 puts f(99) if __FILE__==$0
XXX

test_script = <<XXX
 require 'test/unit';load 'f.rb';class TestByRbtest < Test::Unit::TestCase;
 def setup() end
 
 
 def test1
 assert_equal(100, f(10))
 end
 
 end
XXX

assert_equal unindent(test_script), script_to_test_script(unindent(script), "f.rb")
=end

def script_to_test_script(ruby_source, script_filename)
  _SKIP = "\n"
  state = :start
  ruby_source.lines.map do |line|
    case line
    when /^#!/
      line
    when "=begin rbtest\n"
      preamble = (state == :start) ? first_test(script_filename) : ""
      state = :rbtest_block
      preamble + "\n"
    when /^=begin (test\S*)$/   # test method block
      preamble = (state == :start) ? first_test(script_filename) : ""
      state = :test_method
      preamble + "def #{$1}\n"
    when /^=begin/            # other block
      state = :begin
      _SKIP
    when /^=end$/
      begin
        if state == :test_method
          "end\n"                 # /def test_xxx
        else
          _SKIP
        end
      ensure
        state = :script
      end
    else
      if state == :test_method or state == :rbtest_block
        line
      else
        _SKIP
      end
    end
  end.join + "end\n"            # /class TestByRbtest
end

=begin test_execute_test_script_command
require 'tempfile'
temp = Tempfile.new "rbtest"
test_script_filename = temp.path

temp.puts "#!/usr/bin/ruby18"
temp.flush
assert_equal "/usr/bin/ruby18 #{test_script_filename} -v", execute_test_script_command(test_script_filename, parse_argv!(["a0.rb", "-v"]))

temp.rewind
temp.puts "class XX"
temp.flush
assert_equal "ruby #{test_script_filename} ", execute_test_script_command(test_script_filename, parse_argv!(["a0.rb"]))
assert_equal "ruby18 #{test_script_filename} ", execute_test_script_command(test_script_filename, parse_argv!(["-S", "ruby18", "a0.rb"]))

=end

def shebang(filename)
  open(filename) {|f| line = f.gets.chomp; line[0,2] == "#!" and line[2..-1] }
end

def execute_test_script_command(test_script_filename, args)
  ruby = args.interpreter || shebang(test_script_filename) || "ruby"
  "#{ruby} #{test_script_filename} #{args.opts.join ' '}"
end

def execute_test_script(test_script_filename, args)
  output = `#{execute_test_script_command test_script_filename, args}`
  print output.gsub(Regexp.union(test_script_filename), args.script_filename)
end

def generate_test_script(script_filename, test_script_filename)
  open(test_script_filename, "w") do |f|
    f.write(script_to_test_script(File.read(script_filename), script_filename))
    f.chmod 0755                # executable
  end
end

################
def do_test(args)
  begin
    test_script_filename = "#{args.script_filename}__test__.rb"
    generate_test_script(args.script_filename, test_script_filename)
    execute_test_script(test_script_filename, args)
  ensure
    File.unlink test_script_filename
  end
end

def output_test_script(args)
  print(script_to_test_script(File.read(args.script_filename), args.script_filename))
end

def example(args)
  print File.read($0)
end

def help(args)
  print USAGE
end

=begin test_parse_argv_bang
args = parse_argv!(["a.rb", "-S", "ruby19"])
assert_equal "a.rb", args.script_filename
assert_equal "ruby19", args.interpreter
assert_equal [], args.opts
assert_equal :do_test, args.action

args = parse_argv!(["-S", "ruby19", "a.rb", "-v"])
assert_equal "a.rb", args.script_filename
assert_equal "ruby19", args.interpreter
assert_equal ["-v"], args.opts
assert_equal :do_test, args.action

args = parse_argv!(["a.rb", "-n", "test_0"])
assert_equal "a.rb", args.script_filename
assert_equal nil, args.interpreter
assert_equal ["-n", "test_0"], args.opts
assert_equal :do_test, args.action

assert_equal :help , parse_argv!([]).action
assert_equal :help , parse_argv!(["--help"]).action
assert_equal :help , parse_argv!(["-h"]).action

args = parse_argv!(["--output", "a.rb"])
assert_equal "a.rb", args.script_filename
assert_equal :output_test_script, args.action

assert_equal :example, parse_argv!(["--example"]).action

=end

Args = Struct.new :script_filename, :interpreter, :opts, :action
def parse_argv!(argv)
  args = Args.new
  args.action = :do_test
  argv.delete '--output' and args.action = :output_test_script
  argv.delete '--example' and args.action = :example and return args
  (argv.delete '-h' or argv.delete '--help') and args.action = :help
  i = argv.index "-S"
  if i and args.interpreter=argv[i+1]
    argv.delete_at i+1
    argv.delete_at i
  end
  args.script_filename = argv.shift
  args.opts = argv
  args.script_filename or args.action = :help
  args
end

if __FILE__ ==$0
  args = parse_argv! ARGV
  __send__(args.action, args)
end

