diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bab1fb84f..f8a4e47b5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,17 +25,19 @@ jobs: run: bundle exec rubocop irb: needs: ruby-versions - name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} + name: rake test ${{ matrix.ruby }} ${{ matrix.with_latest_reline && '(latest reline)' || '' }} ${{ matrix.with_tracer && '(with tracer)' || '' }} strategy: matrix: ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} with_latest_reline: [true, false] + with_tracer: [true, false] exclude: - ruby: truffleruby fail-fast: false runs-on: ubuntu-latest env: WITH_LATEST_RELINE: ${{matrix.with_latest_reline}} + WITH_TRACER: ${{matrix.with_tracer}} timeout-minutes: 30 steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 diff --git a/Gemfile b/Gemfile index f4b670cb0..149268561 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,7 @@ gem "test-unit-ruby-core" gem "rubocop" +gem "tracer" if ENV["WITH_TRACER"] == "true" gem "debug", github: "ruby/debug", platforms: [:mri, :mswin] if RUBY_VERSION >= "3.0.0" && !is_truffleruby diff --git a/lib/irb/context.rb b/lib/irb/context.rb index ac61b765c..5b8791c3b 100644 --- a/lib/irb/context.rb +++ b/lib/irb/context.rb @@ -161,6 +161,11 @@ def initialize(irb, workspace = nil, input_method = nil) private_constant :KEYWORD_ALIASES + def use_tracer=(val) + require_relative "ext/tracer" + @use_tracer = val + end + private def build_completor completor_type = IRB.conf[:COMPLETOR] case completor_type diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb index 3eaeb70ef..53b2e6224 100644 --- a/lib/irb/ext/tracer.rb +++ b/lib/irb/ext/tracer.rb @@ -3,76 +3,30 @@ # irb/lib/tracer.rb - # by Keiju ISHITSUKA(keiju@ruby-lang.org) # - +# Loading the gem "tracer" will cause it to extend IRB commands with: +# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb begin require "tracer" rescue LoadError $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." - module IRB - class Context - def use_tracer=(opt) - # do nothing - end - end - end return # This is about to disable loading below end module IRB - - # initialize tracing function - def IRB.initialize_tracer - Tracer.verbose = false - Tracer.add_filter { - |event, file, line, id, binding, *rests| - /^#{Regexp.quote(@CONF[:IRB_LIB_PATH])}/ !~ file and - File::basename(file) != "irb.rb" - } - end - - class Context - # Whether Tracer is used when evaluating statements in this context. - # - # See +lib/tracer.rb+ for more information. - attr_reader :use_tracer - alias use_tracer? use_tracer - - # Sets whether or not to use the Tracer library when evaluating statements - # in this context. - # - # See +lib/tracer.rb+ for more information. - def use_tracer=(opt) - if opt - Tracer.set_get_line_procs(@irb_path) { - |line_no, *rests| - @io.line(line_no) - } - elsif !opt && @use_tracer - Tracer.off - end - @use_tracer=opt - end - end - class WorkSpace alias __evaluate__ evaluate # Evaluate the context of this workspace and use the Tracer library to # output the exact lines of code are being executed in chronological order. # - # See +lib/tracer.rb+ for more information. - def evaluate(context, statements, file = nil, line = nil) - if context.use_tracer? && file != nil && line != nil - Tracer.on - begin + # See https://github.com/ruby/tracer for more information. + def evaluate(statements, file = __FILE__, line = __LINE__) + if IRB.conf[:USE_TRACER] == true + CallTracer.new(colorize: Color.colorable?).start do __evaluate__(statements, file, line) - ensure - Tracer.off end else - __evaluate__(statements, file || __FILE__, line || __LINE__) + __evaluate__(statements, file, line) end end end - - IRB.initialize_tracer end diff --git a/lib/irb/extend-command.rb b/lib/irb/extend-command.rb index 69d83080d..91ca96e91 100644 --- a/lib/irb/extend-command.rb +++ b/lib/irb/extend-command.rb @@ -317,7 +317,6 @@ module ContextExtender @EXTEND_COMMANDS = [ [:eval_history=, "ext/eval_history.rb"], - [:use_tracer=, "ext/tracer.rb"], [:use_loader=, "ext/use-loader.rb"], ] diff --git a/test/irb/test_context.rb b/test/irb/test_context.rb index dad819b4c..a76152169 100644 --- a/test/irb/test_context.rb +++ b/test/irb/test_context.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'tempfile' require 'irb' -require 'rubygems' if defined?(Gem) +require 'rubygems' require_relative "helper" diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 036509813..64bd96d02 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -216,6 +216,12 @@ def test_dash assert_equal(['-f'], argv) end + def test_option_tracer + argv = %w[--tracer] + IRB.setup(eval("__FILE__"), argv: argv) + assert_equal(true, IRB.conf[:USE_TRACER]) + end + private def with_argv(argv) diff --git a/test/irb/test_tracer.rb b/test/irb/test_tracer.rb new file mode 100644 index 000000000..f8da066b6 --- /dev/null +++ b/test/irb/test_tracer.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: false +require 'tempfile' +require 'irb' +require 'rubygems' + +require_relative "helper" + +module TestIRB + class ContextWithTracerIntegrationTest < IntegrationTestCase + def setup + super + + @envs.merge!("NO_COLOR" => "true", "RUBY_DEBUG_HISTORY_FILE" => '') + end + + def example_ruby_file + <<~'RUBY' + class Foo + def self.foo + 100 + end + end + + def bar(obj) + obj.foo + end + + binding.irb + RUBY + end + + def test_use_tracer_is_disabled_by_default + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = false + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_nil IRB.conf[:USER_TRACER] + assert_not_include(output, "#depth:") + assert_not_include(output, "Foo.foo") + end + + def test_use_tracer_enabled_when_gem_is_unavailable + begin + gem 'tracer' + omit "Skipping because 'tracer' gem is available." + rescue Gem::LoadError + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Tracer extension of IRB is enabled but tracer gem wasn't found.") + end + end + + def test_use_tracer_enabled_when_gem_is_available + if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.1.0') + omit "Ruby version before 3.1.0 does not support Tracer integration. Skipping this test." + end + + begin + gem 'tracer' + rescue Gem::LoadError + omit "Skipping because 'tracer' gem is not available. Enable with WITH_TRACER=true." + end + + write_rc <<~RUBY + IRB.conf[:USE_TRACER] = true + RUBY + + write_ruby example_ruby_file + + output = run_ruby_file do + type "bar(Foo)" + type "exit!" + end + + assert_include(output, "Object#bar at") + assert_include(output, "Foo.foo at") + assert_include(output, "Foo.foo #=> 100") + assert_include(output, "Object#bar #=> 100") + end + end +end