debugger-xml 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. data/.gitignore +20 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +24 -0
  5. data/Rakefile +10 -0
  6. data/bin/rdebug-ide +90 -0
  7. data/debugger-xml.gemspec +25 -0
  8. data/lib/debugger/printers/texts/xml.yml +121 -0
  9. data/lib/debugger/printers/xml.rb +195 -0
  10. data/lib/debugger/xml.rb +3 -0
  11. data/lib/debugger/xml/extensions/commands/edit.rb +13 -0
  12. data/lib/debugger/xml/extensions/commands/frame.rb +14 -0
  13. data/lib/debugger/xml/extensions/commands/help.rb +13 -0
  14. data/lib/debugger/xml/extensions/commands/info.rb +13 -0
  15. data/lib/debugger/xml/extensions/commands/irb.rb +13 -0
  16. data/lib/debugger/xml/extensions/commands/kill.rb +13 -0
  17. data/lib/debugger/xml/extensions/commands/tmate.rb +13 -0
  18. data/lib/debugger/xml/extensions/commands/trace.rb +13 -0
  19. data/lib/debugger/xml/extensions/commands/variables.rb +16 -0
  20. data/lib/debugger/xml/extensions/debugger.rb +28 -0
  21. data/lib/debugger/xml/extensions/processor.rb +9 -0
  22. data/lib/debugger/xml/ide_processor.rb +149 -0
  23. data/lib/debugger/xml/interface.rb +65 -0
  24. data/lib/debugger/xml/version.rb +5 -0
  25. data/test/breakpoints_test.rb +190 -0
  26. data/test/conditions_test.rb +32 -0
  27. data/test/continue_test.rb +12 -0
  28. data/test/display_test.rb +25 -0
  29. data/test/edit_test.rb +12 -0
  30. data/test/eval_test.rb +20 -0
  31. data/test/examples/breakpoint1.rb +15 -0
  32. data/test/examples/breakpoint2.rb +7 -0
  33. data/test/examples/conditions.rb +4 -0
  34. data/test/examples/continue.rb +4 -0
  35. data/test/examples/display.rb +5 -0
  36. data/test/examples/edit.rb +3 -0
  37. data/test/examples/eval.rb +4 -0
  38. data/test/examples/frame.rb +31 -0
  39. data/test/examples/help.rb +2 -0
  40. data/test/examples/info.rb +48 -0
  41. data/test/examples/irb.rb +6 -0
  42. data/test/examples/jump.rb +14 -0
  43. data/test/examples/kill.rb +2 -0
  44. data/test/examples/method.rb +15 -0
  45. data/test/examples/reload.rb +6 -0
  46. data/test/examples/restart.rb +6 -0
  47. data/test/examples/set.rb +3 -0
  48. data/test/examples/stepping.rb +21 -0
  49. data/test/examples/thread.rb +32 -0
  50. data/test/examples/tmate.rb +10 -0
  51. data/test/examples/trace.rb +7 -0
  52. data/test/examples/variables.rb +26 -0
  53. data/test/examples/variables_xml.rb +31 -0
  54. data/test/frame_test.rb +29 -0
  55. data/test/help_test.rb +13 -0
  56. data/test/ide_control_command_processor_test.rb +62 -0
  57. data/test/ide_processor_test.rb +118 -0
  58. data/test/info_test.rb +12 -0
  59. data/test/irb_test.rb +12 -0
  60. data/test/jump_test.rb +22 -0
  61. data/test/kill_test.rb +13 -0
  62. data/test/method_test.rb +36 -0
  63. data/test/printers/xml_test.rb +193 -0
  64. data/test/reload_test.rb +14 -0
  65. data/test/restart_test.rb +50 -0
  66. data/test/set_test.rb +12 -0
  67. data/test/stepping_test.rb +15 -0
  68. data/test/test_helper.rb +6 -0
  69. data/test/thread_test.rb +20 -0
  70. data/test/tmate_test.rb +15 -0
  71. data/test/trace_test.rb +12 -0
  72. data/test/variables_test.rb +84 -0
  73. metadata +253 -0
@@ -0,0 +1,3 @@
1
+ require "debugger"
2
+ require "debugger/printers/xml"
3
+ Dir.glob(File.expand_path("../**/*.rb", __FILE__)).each { |f| require f }
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class Edit < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'edit')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,14 @@
1
+ module Debugger
2
+ module FrameFunctions
3
+
4
+ # Mark should be 'true' or 'false', as a String
5
+ def get_pr_arguments_with_xml(mark, *args)
6
+ get_pr_arguments_without_xml((!!mark).to_s, *args)
7
+ end
8
+
9
+ alias_method :get_pr_arguments_without_xml, :get_pr_arguments
10
+ alias_method :get_pr_arguments, :get_pr_arguments_with_xml
11
+
12
+ end
13
+ end
14
+
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class HelpCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'help')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class InfoCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'info')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class IRBCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'irb')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class KillCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'kill')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class TextMateCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'tmate')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Debugger
2
+ class TraceCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ errmsg(pr("general.errors.unsupported", cmd: 'trace')) && return if Debugger.printer.type == "xml"
6
+ execute_without_xml(*args)
7
+ end
8
+
9
+ alias_method :execute_without_xml, :execute
10
+ alias_method :execute, :execute_with_xml
11
+
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ module Debugger
2
+ class VarInstanceCommand < Command
3
+
4
+ def execute_with_xml(*args)
5
+ if Debugger.printer.type == "xml"
6
+ print Debugger.printer.print_instance_variables(get_obj(@match))
7
+ else
8
+ execute_without_xml(*args)
9
+ end
10
+ end
11
+
12
+ alias_method :execute_without_xml, :execute
13
+ alias_method :execute, :execute_with_xml
14
+
15
+ end
16
+ end
@@ -0,0 +1,28 @@
1
+ module Debugger
2
+ class << self
3
+ attr_accessor :wait_for_start
4
+
5
+ def start_remote_ide(host, port)
6
+ return if @control_thread
7
+ @mutex = Mutex.new
8
+ @proceed = ConditionVariable.new
9
+ start
10
+ @control_thread = DebugThread.new do
11
+ server = TCPServer.new(host, port)
12
+ while (session = server.accept)
13
+ interface = IdeInterface.new(session)
14
+ processor = IdeControlCommandProcessor.new(interface)
15
+ self.handler = IdeProcessor.new(interface)
16
+ processor.process_commands
17
+ end
18
+ end
19
+ @mutex.synchronize { @proceed.wait(@mutex) } if wait_for_start
20
+ end
21
+
22
+ def proceed
23
+ return unless @mutex
24
+ @mutex.synchronize { @proceed.signal }
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module Debugger
2
+ class CommandProcessor < Processor
3
+
4
+ def self.annotate_location_and_text(output)
5
+ output
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,149 @@
1
+ require 'ruby-debug/processor'
2
+
3
+ module Debugger
4
+ module Xml
5
+
6
+ class IdeProcessor < Processor
7
+ attr_reader :context, :file, :line, :display
8
+ def initialize(interface)
9
+ @mutex = Mutex.new
10
+ @interface = interface
11
+ @display = []
12
+ end
13
+
14
+ def at_breakpoint(context, breakpoint)
15
+ raise "@last_breakpoint supposed to be nil. is #{@last_breakpoint}" if @last_breakpoint
16
+ # at_breakpoint is immediately followed by #at_line event. So postpone breakpoint printing until #at_line.
17
+ @last_breakpoint = breakpoint
18
+ end
19
+ protect :at_breakpoint
20
+
21
+ # TODO: Catching exceptions doesn't work so far, need to fix
22
+ def at_catchpoint(context, excpt)
23
+ end
24
+
25
+ # We don't have tracing for IDE
26
+ def at_tracing(*args)
27
+ end
28
+
29
+ def at_line(context, file, line)
30
+ if context.nil? || context.stop_reason == :step
31
+ print_file_line(context, file, line)
32
+ end
33
+ line_event(context, file, line)
34
+ end
35
+ protect :at_line
36
+
37
+ def at_return(context, file, line)
38
+ print_file_line(context, file, line)
39
+ context.stop_frame = -1
40
+ line_event(context, file, line)
41
+ end
42
+
43
+ def at_line?
44
+ !!@line
45
+ end
46
+
47
+ private
48
+
49
+ def print_file_line(context, file, line)
50
+ print(
51
+ Debugger.printer.print(
52
+ "stop.suspend",
53
+ file: CommandProcessor.canonic_file(file), line_number: line, line: Debugger.line_at(file, line),
54
+ thnum: context && context.thnum, frames: context && context.stack_size
55
+ )
56
+ )
57
+ end
58
+
59
+ def line_event(context, file, line)
60
+ @line = line
61
+ @file = file
62
+ @context = context
63
+ if @last_breakpoint
64
+ # followed after #at_breakpoint in the same thread. Print breakpoint
65
+ # now when @line, @file and @context are correctly set to prevent race
66
+ # condition with `control thread'.
67
+ n = Debugger.breakpoints.index(@last_breakpoint) + 1
68
+ print pr("breakpoints.stop_at_breakpoint",
69
+ id: n, file: @file, line: @line, thread_id: Debugger.current_context.thnum
70
+ )
71
+ end
72
+ if @context && @context.thread.is_a?(Debugger::DebugThread)
73
+ raise pr("thread.errors.debug_trace", thread: @context.thread)
74
+ end
75
+ # will be resumed by commands like `step', `next', `continue', `finish'
76
+ # from `control thread'
77
+ Thread.stop
78
+ ensure
79
+ @line = nil
80
+ @file = nil
81
+ @context = nil
82
+ @last_breakpoint = nil
83
+ end
84
+ end
85
+
86
+ class IdeControlCommandProcessor < Processor
87
+
88
+ def initialize(interface)
89
+ @interface = interface
90
+ end
91
+
92
+ def process_commands
93
+ control_command_classes = Command.commands.select(&:allow_in_control)
94
+ state = ControlCommandProcessor::State.new(@interface, control_command_classes)
95
+ control_commands = control_command_classes.map { |cmd| cmd.new(state) }
96
+
97
+ while input = @interface.read_command
98
+ split_commands(input).each do |cmd|
99
+ catch(:debug_error) do
100
+ if matched_cmd = control_commands.find { |c| c.match(cmd) }
101
+ matched_cmd.execute
102
+ else
103
+ process_context_commands(cmd)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ rescue IOError, Errno::EPIPE
109
+ rescue Exception
110
+ print "INTERNAL ERROR!!! #{$!}\n" rescue nil
111
+ print $!.backtrace.map{|l| "\t#{l}"}.join("\n") rescue nil
112
+ ensure
113
+ @interface.close
114
+ end
115
+
116
+ private
117
+
118
+ def process_context_commands(input)
119
+ unless Debugger.handler.at_line?
120
+ errmsg pr("base.errors.no_suspended_thread", input: input)
121
+ return
122
+ end
123
+ event_command_classes = Command.commands.select(&:event)
124
+ state = CommandProcessor::State.new do |s|
125
+ s.context = Debugger.handler.context
126
+ s.file = Debugger.handler.file
127
+ s.line = Debugger.handler.line
128
+ s.binding = Debugger.handler.context.frame_binding(0)
129
+ s.interface = @interface
130
+ s.commands = event_command_classes
131
+ end
132
+ event_commands = event_command_classes.map { |cls| cls.new(state) }
133
+ catch(:debug_error) do
134
+ if cmd = event_commands.find { |c| c.match(input) }
135
+ if state.context.dead? && cmd.class.need_context
136
+ print pr("base.errors.command_unavailable")
137
+ else
138
+ cmd.execute
139
+ end
140
+ else
141
+ print pr("base.errors.unknown_command", input: input)
142
+ end
143
+ end
144
+ state.context.thread.run if state.proceed?
145
+ end
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,65 @@
1
+ module Debugger
2
+ module Xml
3
+ class IdeInterface < Interface # :nodoc:
4
+ attr_accessor :command_queue
5
+ attr_accessor :histfile
6
+ attr_accessor :history_save
7
+ attr_accessor :history_length
8
+ attr_accessor :restart_file
9
+
10
+ def initialize(socket)
11
+ @command_queue = []
12
+ @socket = socket
13
+ @history_save = false
14
+ @history_length = 256
15
+ @histfile = ''
16
+ @restart_file = nil
17
+ end
18
+
19
+ def close
20
+ @socket.close
21
+ rescue Exception
22
+ end
23
+
24
+ def print_debug(msg)
25
+ STDOUT.puts(msg)
26
+ end
27
+
28
+ def errmsg(*args)
29
+ print(*args)
30
+ end
31
+
32
+ def confirm(prompt)
33
+ true
34
+ end
35
+
36
+ def finalize
37
+ close
38
+ end
39
+
40
+ # Workaround for JRuby issue http://jira.codehaus.org/browse/JRUBY-2063
41
+ def non_blocking_gets
42
+ loop do
43
+ result, _, _ = IO.select([@socket], nil, nil, 0.2)
44
+ next unless result
45
+ return result[0].gets
46
+ end
47
+ end
48
+
49
+ def read_command(*args)
50
+ result = non_blocking_gets
51
+ raise IOError unless result
52
+ result.chomp
53
+ end
54
+
55
+ def readline_support?
56
+ false
57
+ end
58
+
59
+ def print(*args)
60
+ @socket.printf(*escape_input(args))
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,5 @@
1
+ module Debugger
2
+ module Xml
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,190 @@
1
+ require_relative 'test_helper'
2
+
3
+ describe "Breakpoints" do
4
+ include TestDsl
5
+ temporary_change_method_value(Debugger, :printer, Printers::Xml.new)
6
+
7
+ describe "setting breakpoint in the current file" do
8
+ it "must return right response" do
9
+ enter 'break 10'
10
+ id = nil
11
+ debug_file('breakpoint1') { id = breakpoint.id }
12
+ check_output_includes "<breakpointAdded no=\"#{id}\" location=\"#{fullpath('breakpoint1')}:10\"/>"
13
+ end
14
+ end
15
+
16
+ describe "setting breakpoint to unexisted line" do
17
+ it "must show an error" do
18
+ enter 'break 100'
19
+ debug_file("breakpoint1")
20
+ check_output_includes "<error>There are only 15 lines in file 'breakpoint1.rb'</error>", interface.error_queue
21
+ end
22
+ end
23
+
24
+
25
+ describe "shows a message when stopping at breakpoint" do
26
+ temporary_change_hash_value(Debugger::Command.settings, :basename, false)
27
+
28
+ it "must show a message with full filename" do
29
+ enter 'break 14', 'cont'
30
+ debug_file("breakpoint1")
31
+ check_output_includes "<breakpoint file=\"#{fullpath('breakpoint1')}\" line=\"14\" threadId=\"1\"/>"
32
+ end
33
+
34
+ it "must show a message with basename" do
35
+ enter 'set basename', 'break 14', 'cont'
36
+ debug_file("breakpoint1")
37
+ check_output_includes "<breakpoint file=\"breakpoint1.rb\" line=\"14\" threadId=\"1\"/>"
38
+ end
39
+
40
+ it "must not show <suspended>" do
41
+ enter 'break 14', 'cont'
42
+ debug_file("breakpoint1")
43
+ check_output_doesnt_include /<suspended[^>]+line="14"/
44
+ end
45
+ end
46
+
47
+ describe "set breakpoint in a file when setting breakpoint to unexisted file" do
48
+ before do
49
+ enter "break asf:324"
50
+ debug_file("breakpoint1")
51
+ end
52
+
53
+ it "must show an error" do
54
+ check_output_includes "<error>No source file named asf</error>", interface.error_queue
55
+ end
56
+
57
+ it "must ask about setting breakpoint anyway" do
58
+ check_output_includes "<confirmation>Set breakpoint anyway?</confirmation>", interface.confirm_queue
59
+ end
60
+ end
61
+
62
+ describe "set breakpoint to a method" do
63
+ describe "set breakpoint to an instance method" do
64
+ it "must output show in xml" do
65
+ enter 'break A#b', 'cont'
66
+ id = nil
67
+ debug_file("breakpoint1") { id = breakpoint.id }
68
+ check_output_includes "<breakpointAdded no=\"#{id}\" method=\"A::b\"/>"
69
+ end
70
+ end
71
+
72
+ describe "set breakpoint to unexisted class" do
73
+ it "must show an error" do
74
+ enter "break B.a"
75
+ debug_file("breakpoint1")
76
+ check_output_includes "<error>Unknown class B</error>", interface.error_queue
77
+ end
78
+ end
79
+ end
80
+
81
+ describe "set breakpoint to an invalid location" do
82
+ it "must show an error" do
83
+ enter "break foo"
84
+ debug_file("breakpoint1")
85
+ check_output_includes '<error>Invalid breakpoint location: foo</error>', interface.error_queue
86
+ end
87
+ end
88
+
89
+
90
+ describe "disabling a breakpoint" do
91
+ it "must show success message" do
92
+ enter "break 14", ->{"disable #{breakpoint.id}"}, "break 15"
93
+ id = nil
94
+ debug_file("breakpoint1") { id = breakpoint.id }
95
+ check_output_includes "<breakpointDisabled bp_id=\"#{id}\"/>"
96
+ end
97
+
98
+ describe "errors" do
99
+ it "must show an error if syntax is incorrect" do
100
+ enter "disable"
101
+ debug_file("breakpoint1")
102
+ check_output_includes(
103
+ "<error>'disable' must be followed 'display', 'breakpoints' or breakpoint numbers</error>",
104
+ interface.error_queue
105
+ )
106
+ end
107
+
108
+ it "must show an error if no breakpoints is set" do
109
+ enter "disable 1"
110
+ debug_file("breakpoint1")
111
+ check_output_includes '<error>No breakpoints have been set</error>', interface.error_queue
112
+ end
113
+
114
+ it "must show an error if not a number is provided as an argument to 'disable' command" do
115
+ enter "break 14", "disable foo"
116
+ debug_file("breakpoint1")
117
+ check_output_includes "<error>Disable breakpoints argument 'foo' needs to be a number</error>"
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "enabling a breakpoint" do
123
+ it "must show success message" do
124
+ enter "break 14", ->{"enable #{breakpoint.id}"}, "break 15"
125
+ id = nil
126
+ debug_file("breakpoint1") { id = breakpoint.id }
127
+ check_output_includes "<breakpointEnabled bp_id=\"#{id}\"/>"
128
+ end
129
+
130
+ it "must show an error if syntax is incorrect" do
131
+ enter "enable"
132
+ debug_file("breakpoint1")
133
+ check_output_includes(
134
+ "<error>'enable' must be followed 'display', 'breakpoints' or breakpoint numbers</error>",
135
+ interface.error_queue
136
+ )
137
+ end
138
+ end
139
+
140
+ describe "deleting a breakpoint" do
141
+ it "must show a success message" do
142
+ breakpoint_id = nil
143
+ enter "break 14", ->{breakpoint_id = breakpoint.id; "delete #{breakpoint_id}"}, "break 15"
144
+ debug_file("breakpoint1")
145
+ check_output_includes "<breakpointDeleted no=\"#{breakpoint_id}\"/>"
146
+ end
147
+ end
148
+
149
+ describe "Conditional breakpoints" do
150
+ it "must show an error when conditional syntax is wrong" do
151
+ enter "break 14 ifa b == 3", "break 15", "cont"
152
+ debug_file("breakpoint1") { state.line.must_equal 15 }
153
+ check_output_includes(
154
+ "<error>Expecting 'if' in breakpoint condition; got: ifa b == 3</error>",
155
+ interface.error_queue
156
+ )
157
+ end
158
+
159
+ describe "enabling with wrong conditional syntax" do
160
+ it "must show an error" do
161
+ enter(
162
+ "break 14",
163
+ ->{"disable #{breakpoint.id}"},
164
+ ->{"cond #{breakpoint.id} b -=( 3"},
165
+ ->{"enable #{breakpoint.id}"}
166
+ )
167
+ debug_file("breakpoint1")
168
+ check_output_includes(
169
+ "<error>Expression 'b -=( 3' syntactically incorrect; breakpoint remains disabled</error>",
170
+ interface.error_queue
171
+ )
172
+ end
173
+ end
174
+
175
+ it "must show an error if no file or line is specified" do
176
+ enter "break ifa b == 3", "break 15", "cont"
177
+ debug_file("breakpoint1") { state.line.must_equal 15 }
178
+ check_output_includes "<error>Invalid breakpoint location: ifa b == 3</error>", interface.error_queue
179
+ end
180
+
181
+ it "must show an error if expression syntax is invalid" do
182
+ enter "break if b -=) 3", "break 15", "cont"
183
+ debug_file("breakpoint1") { state.line.must_equal 15 }
184
+ check_output_includes(
185
+ "<error>Expression 'b -=) 3' syntactically incorrect; breakpoint disabled</error>",
186
+ interface.error_queue
187
+ )
188
+ end
189
+ end
190
+ end