diff --git a/.gitignore b/.gitignore index 413493d..c6a664f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,7 @@ ext/ruby_debug/ruby_debug.c ext/ruby_debug/ruby_debug.h Gemfile.lock + +.ruby-version +.ruby-gemset + diff --git a/Guardfile b/Guardfile new file mode 100644 index 0000000..96b19a7 --- /dev/null +++ b/Guardfile @@ -0,0 +1,9 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +guard :minitest do + # with Minitest::Unit + watch(%r{^test/(.*)\/?(.*)_test\.rb}) + watch(%r{^lib/(.*/)?([^/]+)\.rb}) { 'test' } + watch(%r{^test/test_helper\.rb}) { 'test' } +end diff --git a/README.md b/README.md index 708f2e9..eb5b383 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,8 @@ Please report them [on github](http://github.com/cldwalker/debugger/issues). [See here](http://tagaholic.me/contributing.html) for contribution policies. Let's keep this working for the ruby community! +* After forking the repo, run `rake compile` before running `rake test`. + ## Related projects * [debugger-completion](https://github.com/cldwalker/debugger-completion) - autocompletion for diff --git a/Rakefile b/Rakefile index b742ed1..4730642 100644 --- a/Rakefile +++ b/Rakefile @@ -10,7 +10,7 @@ SO_NAME = "ruby_debug.so" desc "Run new MiniTest tests." task :test do Rake::TestTask.new(:test) do |t| - t.test_files = FileList["test/*_test.rb"] + t.test_files = FileList["test/**/*_test.rb"] t.verbose = true end end diff --git a/bin/rdebug b/bin/rdebug index bd99d85..267908e 100755 --- a/bin/rdebug +++ b/bin/rdebug @@ -25,7 +25,7 @@ # --cport options # #--cport=port:: -# Use port port for access to debugger control. +# Use port port for access to debugger control. Default 8990 # #-d | --debug:: # Set $DEBUG true. @@ -55,7 +55,7 @@ # Do not stop when script is loaded. # #-p | --port=PORT:: -# Host name used for remote debugging. +# Host name used for remote debugging. Default 8989 # #-r | --requirescript:: # Require the library, before executing your script. @@ -105,6 +105,7 @@ require 'optparse' require 'ostruct' require 'debugger' +require_relative '../lib/debugger/runner/rdebug_option_parser' def debug_program(options) # Make sure Ruby script syntax checks okay. @@ -143,140 +144,6 @@ def whence_file(prog_script) return prog_script end -options = OpenStruct.new( - 'annotate' => Debugger.annotate, - 'client' => false, - 'control' => true, - 'cport' => Debugger::PORT + 1, - 'host' => nil, - 'quit' => true, - 'no_rewrite_program' => false, - 'stop' => true, - 'nx' => false, - 'port' => Debugger::PORT, - 'restart_script' => nil, - 'script' => nil, - 'server' => false, - 'tracing' => false, - 'verbose_long' => false, - 'wait' => false -) - -def process_options(options) - program = File.basename($0) - opts = OptionParser.new do |opts| - opts.banner = < -- -EOB - opts.separator "" - opts.separator "Options:" - opts.on("-A", "--annotate LEVEL", Integer, "Set annotation level") do - |annotate| - Debugger.annotate = annotate - end - opts.on("-c", "--client", "Connect to remote debugger") do - options.client = true - end - opts.on("--cport PORT", Integer, "Port used for control commands") do - |cport| - options.cport = cport - end - opts.on("-d", "--debug", "Set $DEBUG=true") {$DEBUG = true} - opts.on("--emacs LEVEL", Integer, - "Activates full Emacs support at annotation level LEVEL") do - |level| - Debugger.annotate = level.to_i - ENV['EMACS'] = '1' - ENV['COLUMNS'] = '120' if ENV['COLUMNS'].to_i < 120 - options.control = false - options.quit = false - end - opts.on('--emacs-basic', 'Activates basic Emacs mode') do - ENV['EMACS'] = '1' - end - opts.on('-h', '--host HOST', 'Host name used for remote debugging') do - |host| - options.host = host - end - opts.on('-I', '--include PATH', String, 'Add PATH to $LOAD_PATH') do |path| - $LOAD_PATH.unshift(path) - end - opts.on('--no-control', 'Do not automatically start control thread') do - options.control = false - end - opts.on('--no-quit', 'Do not quit when script finishes') do - options.quit = false - end - opts.on('--no-rewrite-program', - 'Do not set $0 to the program being debugged') do - options.no_rewrite_program = true - end - opts.on('--no-stop', 'Do not stop when script is loaded') do - options.stop = false - end - opts.on('-nx', 'Not run debugger initialization files (e.g. .rdebugrc') do - options.nx = true - end - opts.on('-p', '--port PORT', Integer, 'Port used for remote debugging') do - |port| - options.port = port - end - opts.on('-r', '--require SCRIPT', String, - 'Require the library, before executing your script') do |name| - if name == 'debug' - puts "debugger is not compatible with Ruby's 'debug' library. This option is ignored." - else - require name - end - end - opts.on('--restart-script FILE', String, - 'Name of the script file to run. Erased after read') do - |restart_script| - options.restart_script = restart_script - unless File.exists?(options.restart_script) - puts "Script file '#{options.restart_script}' is not found" - exit - end - end - opts.on('--script FILE', String, 'Name of the script file to run') do - |script| - options.script = script - unless File.exists?(options.script) - puts "Script file '#{options.script}' is not found" - exit - end - end - opts.on('-s', '--server', 'Listen for remote connections') do - options.server = true - end - opts.on('-w', '--wait', 'Wait for a client connection, implies -s option') do - options.wait = true - end - opts.on('-x', '--trace', 'Turn on line tracing') {options.tracing = true} - opts.separator '' - opts.separator 'Common options:' - opts.on_tail('--help', 'Show this message') do - puts opts - exit - end - opts.on_tail('--version', - 'Print the version') do - puts "debugger #{Debugger::VERSION}" - exit - end - opts.on('--verbose', 'Turn on verbose mode') do - $VERBOSE = true - options.verbose_long = true - end - opts.on_tail('-v', - 'Print version number, then turn on verbose mode') do - puts "debugger #{Debugger::VERSION}" - $VERBOSE = true - end - end - return opts -end # What file is used for debugger startup commands. unless defined?(OPTS_INITFILE) @@ -297,21 +164,21 @@ begin rescue end -opts = process_options(options) +if not defined? Debugger::ARGV + Debugger::ARGV = ARGV.clone +end +rdebug_path = File.expand_path($0) +if RUBY_PLATFORM =~ /mswin/ + rdebug_path += '.cmd' unless rdebug_path =~ /\.cmd$/i +end +Debugger::RDEBUG_SCRIPT = rdebug_path +Debugger::RDEBUG_FILE = __FILE__ +Debugger::INITIAL_DIR = Dir.pwd + begin - if not defined? Debugger::ARGV - Debugger::ARGV = ARGV.clone - end - rdebug_path = File.expand_path($0) - if RUBY_PLATFORM =~ /mswin/ - rdebug_path += '.cmd' unless rdebug_path =~ /\.cmd$/i - end - Debugger::RDEBUG_SCRIPT = rdebug_path - Debugger::RDEBUG_FILE = __FILE__ - Debugger::INITIAL_DIR = Dir.pwd - opts.parse! ARGV + options = RdebugOptionParser.instance.parse ARGV rescue StandardError => e - puts opts + puts RdebugOptionParser.instance puts puts e.message exit(-1) @@ -322,7 +189,8 @@ if options.client else if ARGV.empty? exit if $VERBOSE and not options.verbose_long - puts opts + # Print the possible options and exit + puts RdebugOptionParser.instance puts puts 'Must specify a script to run' exit(-1) diff --git a/debugger.gemspec b/debugger.gemspec index 766ac20..babe131 100644 --- a/debugger.gemspec +++ b/debugger.gemspec @@ -26,5 +26,6 @@ handling, bindings for stack frames among other things. s.add_development_dependency 'rake-compiler', '~> 0.8.0' s.add_development_dependency 'minitest', '~> 2.12.1' s.add_development_dependency 'mocha', '~> 0.13.0' + s.add_development_dependency 'guard-minitest' s.license = "BSD" end diff --git a/lib/debugger/runner/rdebug_option_parser.rb b/lib/debugger/runner/rdebug_option_parser.rb new file mode 100644 index 0000000..fa18da5 --- /dev/null +++ b/lib/debugger/runner/rdebug_option_parser.rb @@ -0,0 +1,175 @@ +require 'optparse' +require 'ostruct' +require 'singleton' + +# Singleton parser class for Rdebug options +# +# Usage: +# +# RdebugOptionParser.instance.parse arguments +# +# Returns an OpenStruct with attributes built from (in order of priority) +# 1. arguments passed to parse() +# 2. default_options +class RdebugOptionParser + include Singleton + + # arguments - an array of strings, the same format as ARGV + def parse arguments + begin + @option_parser.parse! arguments + rescue StandardError => e + puts @option_parser + puts + puts e.message + exit(-1) + end + + @options + end + + def initialize + @options = default_options + + program = File.basename($0) + @option_parser = OptionParser.new do |parser| + parser.banner = < -- +EOB + parser.separator "" + parser.separator "Options:" + parser.on("-A", "--annotate LEVEL", Integer, + "Set annotation level") do |annotate| + Debugger.annotate = annotate + end + parser.on("-c", "--client", "Connect to remote debugger") do + @options.client = true + end + parser.on("--cport PORT", Integer, "Port used for control commands. Default 8990") do + |cport| + @options.cport = cport + end + parser.on("-d", "--debug", "Set $DEBUG=true") {$DEBUG = true} + parser.on("--emacs LEVEL", Integer, + "Activates full Emacs support at annotation level LEVEL") do + |level| + Debugger.annotate = level.to_i + ENV['EMACS'] = '1' + ENV['COLUMNS'] = '120' if ENV['COLUMNS'].to_i < 120 + @options.control = false + @options.quit = false + end + parser.on('--emacs-basic', 'Activates basic Emacs mode') do + ENV['EMACS'] = '1' + end + parser.on('-h', '--host HOST', 'Host name used for remote debugging') do + |host| + @options.host = host + end + parser.on('-I', '--include PATH', String, 'Add PATH to $LOAD_PATH') do |path| + $LOAD_PATH.unshift(path) + end + parser.on('--no-control', 'Do not automatically start control thread') do + @options.control = false + end + parser.on('--no-quit', 'Do not quit when script finishes') do + @options.quit = false + end + parser.on('--no-rewrite-program', + 'Do not set $0 to the program being debugged') do + @options.no_rewrite_program = true + end + parser.on('--no-stop', 'Do not stop when script is loaded') do + @options.stop = false + end + parser.on('-nx', 'Not run debugger initialization files (e.g. .rdebugrc') do + @options.nx = true + end + parser.on('-p', '--port PORT', Integer, 'Port used for remote debugging. Default 8989') do + |port| + @options.port = port + end + parser.on('-r', '--require SCRIPT', String, + 'Require the library, before executing your script') do |name| + if name == 'debug' + puts "debugger is not compatible with Ruby's 'debug' library. This option is ignored." + else + require name + end + end + parser.on('--restart-script FILE', String, + 'Name of the script file to run. Erased after read') do + |restart_script| + @options.restart_script = restart_script + unless File.exists?(@options.restart_script) + puts "Script file '#{@options.restart_script}' is not found" + exit + end + end + parser.on('--script FILE', String, 'Name of the script file to run') do + |script| + @options.script = script + unless File.exists?(@options.script) + puts "Script file '#{@options.script}' is not found" + exit + end + end + parser.on('-s', '--server', 'Listen for remote connections') do + @options.server = true + end + parser.on('-w', '--wait', 'Wait for a client connection, implies -s option') do + @options.wait = true + end + parser.on('-x', '--trace', 'Turn on line tracing') {@options.tracing = true} + parser.separator '' + parser.separator 'Common options:' + parser.on_tail('--help', 'Show this message') do + puts parser + exit + end + parser.on_tail('--version', + 'Print the version') do + puts "debugger #{Debugger::VERSION}" + exit + end + parser.on('--verbose', 'Turn on verbose mode') do + $VERBOSE = true + @options.verbose_long = true + end + parser.on_tail('-v', + 'Print version number, then turn on verbose mode') do + puts "debugger #{Debugger::VERSION}" + $VERBOSE = true + end + end + end + + # Delegate to_s to @option_parser, which returns an option summary string + def to_s + @option_parser.to_s + end + + private + + def default_options + OpenStruct.new( + 'annotate' => Debugger.annotate, + 'client' => false, + 'control' => true, + 'cport' => Debugger::PORT + 1, + 'host' => nil, + 'quit' => true, + 'no_rewrite_program' => false, + 'stop' => true, + 'nx' => false, + 'port' => Debugger::PORT, + 'restart_script' => nil, + 'script' => nil, + 'server' => false, + 'tracing' => false, + 'verbose_long' => false, + 'wait' => false + ) + end +end diff --git a/man/rdebug.1 b/man/rdebug.1 index 4c9d03f..0384aed 100644 --- a/man/rdebug.1 +++ b/man/rdebug.1 @@ -159,7 +159,7 @@ Connect to a remote debugger. Used with another rdebug invocation using \-\-serv See also \-\-host and \-\-cport options .TP .B \-\-cport=PORT -Port used for control commands. +Port used for control commands. Default 8990 .TP .B \-d | \-\-debug Set $DEBUG true. @@ -189,7 +189,7 @@ Do not automatically start control thread. Do not stop when script is loaded. .TP .B \-p | \-\-port=PORT -Host name used for remote debugging. +Host name used for remote debugging. Default 8989 .TP .B \-r | \-\-require SCRIPT Require the library, before executing your script. diff --git a/test/new/printers/plain_test.rb b/test/new/printers/plain_test.rb index 4fb6a63..6580163 100644 --- a/test/new/printers/plain_test.rb +++ b/test/new/printers/plain_test.rb @@ -1,4 +1,4 @@ -require_relative '../test_helper' +require_relative '../../test_helper' describe "Printers::Plain" do include PrinterHelpers diff --git a/test/rdebug/rdebug_option_parser_test.rb b/test/rdebug/rdebug_option_parser_test.rb new file mode 100644 index 0000000..075b001 --- /dev/null +++ b/test/rdebug/rdebug_option_parser_test.rb @@ -0,0 +1,283 @@ +require_relative '../test_helper' +require_relative '../../lib/debugger/runner/rdebug_option_parser' +require 'minitest/autorun' +require 'mocha/setup' + +describe RdebugOptionParser do + before do + # Hack to re-initialize a Singleton class and clear its state + Singleton.__init__(RdebugOptionParser) + end + + it "returns an option summary" do + RdebugOptionParser.instance.to_s.wont_be_empty + end + + describe "annotation arguments" do + it "parses the long form" do + Debugger.expects(:annotate=).with(3) + call_rdebug_parse_with_arguments ["--annotate=3"] + end + + it "parses the short form" do + Debugger.expects(:annotate=).with(3) + call_rdebug_parse_with_arguments ["-A", "3"] + end + end + + describe "client mode arguments" do + it "parses the long form" do + result = call_rdebug_parse_with_arguments ["--client"] + result.client.must_equal true + end + + it "parses the short form" do + result = call_rdebug_parse_with_arguments ["-c"] + result.client.must_equal true + end + end + + it "parses a control port argument and sets a value in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ["--cport=3000"] + result.cport.must_equal 3000 + end + + describe "debug arguments" do + before { @old_debug_setting = $DEBUG } + after { $DEBUG = @old_debug_setting } + it "parses the long form" do + $DEBUG = false + call_rdebug_parse_with_arguments ["--debug"] + $DEBUG.must_equal true + end + + it "parses the short form" do + $DEBUG = false + call_rdebug_parse_with_arguments ["-d"] + $DEBUG.must_equal true + end + end + + describe "emacs arguments" do + it "parses an emacs option with custom annotation level" do + Debugger.expects(:annotate=).with(5) + ENV.expects(:[]=).with('EMACS', '1') + + # Mock column width less than 120 + ENV.expects(:[]).with('COLUMNS').returns(110) + ENV.expects(:[]=).with('COLUMNS', '120') + + result = call_rdebug_parse_with_arguments ['--emacs=5'] + result.control.must_equal false + result.quit.must_equal false + end + + it "parses an emacs basic argument" do + ENV.expects(:[]=).with('EMACS', '1') + + call_rdebug_parse_with_arguments ['--emacs-basic'] + end + end + + describe "host arguments" do + it "parses the short form" do + result = call_rdebug_parse_with_arguments ['-h', '5000'] + result.host.must_equal '5000' + end + + it "parses the long form" do + result = call_rdebug_parse_with_arguments ['--host=5000'] + result.host.must_equal '5000' + end + end + + describe "include arguments" do + before { $LOAD_PATH.expects(:unshift).with('./') } + it "parses the long form" do + call_rdebug_parse_with_arguments ['--include=./'] + end + + it "parses the short form" do + call_rdebug_parse_with_arguments ['-I', './'] + end + end + + it "parses a no-control-thread argument and sets a boolean in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--no-control'] + result.control.must_equal false + end + + it "parses a no-quit argument and sets a boolean in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--no-quit'] + result.quit.must_equal false + end + + it "parses a no-rewrite-program argument and sets a boolean in returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--no-rewrite-program'] + result.no_rewrite_program.must_equal true + end + + it "parses a no-stop argument and sets a boolean in returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--no-stop'] + result.stop.must_equal false + end + + it "parses a skip init files argument and sets a boolean in returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['-nx'] + result.nx.must_equal true + end + + it "parses a port argument and sets a user-provided value in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['-p', '5000'] + result.port.must_equal 5000 + end + + describe "require arguments" do + it "parses the long form" do + RdebugOptionParser.instance.expects(:require).with('foo').returns(true) + call_rdebug_parse_with_arguments ["--require=foo"] + end + + it "parses the short form" do + RdebugOptionParser.instance.expects(:require).with('foo').returns(true) + call_rdebug_parse_with_arguments ["-r", "foo"] + end + end + + describe "restart-script" do + it "sets the filename in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--restart-script=./'] + result.restart_script.must_equal './' + end + + it "checks for the existence of a file and prints an error message if it doesn't exist" do + out, err = capture_io do + assert_raises(SystemExit) do + RdebugOptionParser.instance.parse ['--restart-script=./non_existent_file'] + end + end + + out.must_match /is not found/ + end + end + + describe "script" do + it "sets the filename in the returned OpenStruct" do + result = call_rdebug_parse_with_arguments ['--script=./'] + result.script.must_equal './' + end + + it "checks for the existence of a file and prints an error message if it doesn't exist" do + out, err = capture_io do + assert_raises(SystemExit) do + RdebugOptionParser.instance.parse ['--script=./non_existent_file'] + end + end + + out.must_match /is not found/ + end + end + + describe "server mode arguments" do + it "parses the long form" do + result = call_rdebug_parse_with_arguments ["--server"] + result.server.must_equal true + end + + it "parses the short form" do + result = call_rdebug_parse_with_arguments ["-s"] + result.server.must_equal true + end + end + + describe "wait mode arguments" do + it "parses the long form" do + result = call_rdebug_parse_with_arguments ["--wait"] + result.wait.must_equal true + end + + it "parses the short form" do + result = call_rdebug_parse_with_arguments ["-w"] + result.wait.must_equal true + end + end + + describe "trace arguments" do + after { @result.tracing.must_equal true } + + it "parses the long version and sets a boolean on the returned OpenStruct" do + @result = call_rdebug_parse_with_arguments ["-x"] + end + + it "parses the short version and sets a boolean on the returned OpenStruct" do + @result = call_rdebug_parse_with_arguments ["--trace"] + end + end + + describe "help argument" do + + it "prints the command line options" do + out,err = capture_io do + call_rdebug_parse_with_arguments ['--help'] + end + + out.must_include "Usage:" + end + end + + it 'parses a version argument' do + out, err = capture_io do + assert_raises(SystemExit) do + RdebugOptionParser.instance.parse ['--version'] + end + end + + out.must_include Debugger::VERSION + end + + describe 'verbose options' do + before do + # preserve $VERBOSE setting so that this test doesn't make other tests + # noisy + @verbose_before = $VERBOSE + end + + after do + $VERBOSE = @verbose_before + end + + it 'parses a verbose argument' do + options = RdebugOptionParser.instance.parse ['--verbose'] + + options.verbose_long.must_equal true + $VERBOSE.must_equal true + end + + it 'parses a verbose/version argument and prints a message' do + out, err = capture_io do + options = RdebugOptionParser.instance.parse ['-v'] + + $VERBOSE.must_equal true + end + + out.must_include Debugger::VERSION + end + end + + it "to_s returns the command line options when run without arguments" do + output = RdebugOptionParser.instance.to_s + + output.must_include "Usage:" + output.must_include "[options] -- " + output.must_include "-A, --annotate LEVEL Set annotation level" + end + + private + def call_rdebug_parse_with_arguments arguments + begin + RdebugOptionParser.instance.parse arguments + # RdebugOptionParser catches all StandardErrors and calls exit(-1) + rescue SystemExit + end + end +end