File: utils.rb

package info (click to toggle)
ruby-byebug 11.1.3-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,252 kB
  • sloc: ruby: 8,835; ansic: 1,662; sh: 6; makefile: 4
file content (281 lines) | stat: -rw-r--r-- 7,193 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# frozen_string_literal: true

require "support/matchers"
require "support/temporary"
require "open3"

module Byebug
  #
  # Misc tools for the test suite
  #
  module TestUtils
    include TestMatchers
    include TestTemporary

    #
    # Adds commands to the input queue, so they will be later retrieved by
    # Processor, i.e., it emulates user's input.
    #
    # If a command is a Proc object, it will be executed before being retrieved
    # by Processor. May be handy when you need build a command depending on the
    # current context.
    #
    # @example
    #
    #   enter "b 12", "cont"
    #   enter "b 12", ->{ "disable #{breakpoint.id}" }, "cont"
    #
    def enter(*messages)
      interface.input.concat(messages)
    end

    #
    # Runs the code block passed as a string.
    #
    # The string is copied to a new file and then that file is run. This is
    # done, instead of using `instance_eval` or similar techniques, because
    # it's useful to load a real file in order to make assertions on backtraces
    # or file names.
    #
    # @param program String containing Ruby code to be run. This string could
    # be any valid Ruby code, but in order to avoid redefinition warnings in
    # the test suite, it should define at most one class inside the Byebug
    # namespace. The name of this class is defined by the +example_class+
    # method.
    #
    # @param &block Optional proc which will be executed when Processor
    # extracts all the commands from the input queue. You can use that for
    # making assertions on the current test. If you specified the block and it
    # was never executed, the test will fail.
    #
    # @example
    #
    #   enter "next"
    #   prog <<-RUBY
    #     byebug
    #     puts "hello"
    #   RUBY
    #
    #   debug_code(prog) { assert_equal 3, frame.line }
    #
    def debug_code(program, &block)
      interface.test_block = block
      debug_in_temp_file(program)
      interface.test_block&.call
      interface.test_block = nil
    end

    #
    # Writes a string containing Ruby code to a file and then debugs that file.
    #
    # @param program [String] Ruby code to be debugged
    #
    def debug_in_temp_file(program)
      example_file.write(program)
      example_file.close

      load(example_path)
    end

    #
    # Strips line numbers from a here doc containing ruby code.
    #
    # @param str_with_ruby_code A here doc containing lines of ruby code, each
    # one labeled with a line number
    #
    # @example
    #
    #   strip_line_numbers <<-EOF
    #     1:  puts "hello"
    #     2:  puts "bye"
    #   EOF
    #
    #   returns
    #
    #   puts "hello"
    #   puts "bye"
    #
    def strip_line_numbers(str_with_ruby_code)
      str_with_ruby_code.gsub(/  *\d+: ? ?/, "")
    end

    #
    # Split a string (normally a here doc containing byebug's output) into
    # stripped lines
    #
    # @param output_str [String]
    #
    # @example
    #
    #   split_lines <<-EOF
    #     Sample command
    #
    #     It does an amazing thing.
    #   EOF
    #
    #   returns
    #
    #   ["Sample command", "It does an amazing thing."]
    #
    def split_lines(output_str)
      output_str.split("\n").map(&:strip)
    end

    #
    # Prepares a string to get feed to an assertion accepting arrays of
    # Regexp's. The string is split into lines and each of them is converted to
    # a regexp, properly escaping it and ignoring whitespace.
    #
    # @param output_str [String]
    #
    def prepare_for_regexp(output_str)
      split_lines(output_str).map do |str|
        Regexp.new(Regexp.escape(str), Regexp::EXTENDED)
      end
    end

    #
    # Shortcut to Byebug's interface
    #
    def interface
      Context.interface
    end

    #
    # Shortcut to Byebug's context
    #
    def context
      Byebug.current_context
    end

    #
    # Shortcut to current frame
    #
    def frame
      context.frame
    end

    #
    # Removes all (both enabled and disabled) displays
    #
    def clear_displays
      loop do
        break if Byebug.displays.empty?

        Byebug.displays.pop
      end
    end

    #
    # Remove +const+ from +klass+ without a warning
    #
    def force_remove_const(klass, const)
      klass.send(:remove_const, const) if klass.const_defined?(const)
    end

    #
    # Modifies a line number in a file with new content.
    #
    # @param filename File to be changed
    # @param lineno Line number to be changed
    # @param new_line New line content
    #
    def change_line(file, lineno, new_line)
      lines = File.readlines(file).tap { |c| c[lineno - 1] = "#{new_line}\n" }

      File.open(file, "w") { |f| f.write(lines.join) }
    end

    #
    # Replaces line number <lineno> in file <file> with content <content>
    #
    # @param lineno Line number of line to be replaced.
    # @param file File containing the line to be replaced.
    # @param content New content for the line.
    # @param cmd Command to be run right after changing the line.
    #
    def cmd_after_replace(file, lineno, content, cmd)
      change_line(file, lineno, content)
      cmd
    end

    #
    # A minimal program that gives you a byebug's prompt
    #
    def minimal_program
      <<-RUBY
        module Byebug
          byebug

          "Hello world"
        end
      RUBY
    end

    #
    # Runs program <cmd> in a subprocess feeding it with some input <input> and
    # returns the output of the program.
    #
    # @param cmd [Array] Command line to be run.
    # @param input [String] Input string to feed to the program.
    #
    # @return Program's output
    #
    def run_program(cmd, input = "")
      stdout, = Open3.capture2e(shell_out_env, *cmd, stdin_data: input)

      stdout
    end

    #
    # Runs byebug in a subprocess feeding it with some input <input> and with
    # environment <env>.
    #
    # @param env [Hash] Environment to be passed to the subprocess.
    # @param *args [Array] Args to be passed to byebug.
    # @param input [String] Input string to feed to byebug.
    #
    # @return Byebug's output
    #
    def run_byebug(*args, input: "")
      run_program([*binstub, *args], input)
    end

    #
    # Common environment shared by specs that shell out. It needs to:
    #
    # * Adds byebug to the LOAD_PATH.
    # * (Optionally) Setup coverage tracking so that coverage in the subprocess
    #   is tracked.
    #
    def shell_out_env(simplecov: true)
      minitest_test = Thread.current.backtrace_locations.find do |location|
        location.label.start_with?("test_")
      end

      lib_dir = File.expand_path("../../lib", __dir__)

      base = {
        "MINITEST_TEST" => "#{self.class}##{minitest_test.label}",
        "RUBYOPT" => "-I #{lib_dir}"
      }

      if simplecov
        test_dir = File.expand_path("..", __dir__)
        base["RUBYOPT"] += " -r #{test_dir}/support/simplecov.rb"
      end

      base
    end

    #
    # Binstub command used to run byebug in standalone mode during tests
    #
    def binstub
      cmd = "exe/byebug"
      #return [cmd] unless Gem.win_platform?

      return [RbConfig.ruby, cmd]
    end
  end
end