require 'pathname' require_relative '../helper' describe "edit" do before do @old_editor = Pry.config.editor @file = @line = @contents = nil Pry.config.editor = lambda do |file, line| @file = file; @line = line; @contents = File.read(@file) nil end end after do Pry.config.editor = @old_editor end describe "with FILE" do before do # OS-specific tempdir name. For GNU/Linux it's "tmp", for Windows it's # something "Temp". @tf_dir = if Pry::Helpers::BaseHelpers.mri_19? Pathname.new(Dir::Tmpname.tmpdir) else Pathname.new(Dir.tmpdir) end @tf_path = File.expand_path(File.join(@tf_dir.to_s, 'bar.rb')) FileUtils.touch(@tf_path) end after do FileUtils.rm(@tf_path) if File.exists?(@tf_path) end it "should not allow patching any known kind of file" do ["file.rb", "file.c", "file.py", "file.yml", "file.gemspec", "/tmp/file", "\\\\Temp\\\\file"].each do |file| proc { pry_eval "edit -p #{file}" }.should.raise(NotImplementedError). message.should =~ /Cannot yet patch false objects!/ end end it "should invoke Pry.config.editor with absolutified filenames" do pry_eval 'edit lib/pry.rb' @file.should == File.expand_path('lib/pry.rb') pry_eval "edit #@tf_path" @file.should == @tf_path end it "should guess the line number from a colon" do pry_eval 'edit lib/pry.rb:10' @line.should == 10 end it "should use the line number from -l" do pry_eval 'edit -l 10 lib/pry.rb' @line.should == 10 end it "should not delete the file!" do pry_eval 'edit Rakefile' File.exist?(@file).should == true end it "works with files that contain blanks in their names" do tf_path = File.join(File.dirname(@tf_path), 'swoop and doop.rb') FileUtils.touch(tf_path) pry_eval "edit #{ tf_path }" @file.should == tf_path FileUtils.rm(tf_path) end if respond_to?(:require_relative, true) it "should work with require relative" do Pry.config.editor = lambda { |file, line| File.open(file, 'w'){ |f| f << 'require_relative "baz.rb"' } File.open(file.gsub('bar.rb', 'baz.rb'), 'w'){ |f| f << "Pad.required = true; FileUtils.rm(__FILE__)" } if defined?(Rubinius::Compiler) File.unlink Rubinius::Compiler.compiled_name file end nil } pry_eval "edit #@tf_path" Pad.required.should == true end end describe do before do Pad.counter = 0 Pry.config.editor = lambda { |file, line| File.open(file, 'w') { |f| f << "Pad.counter = Pad.counter + 1" } nil } end it "should reload the file if it is a ruby file" do temp_file do |tf| counter = Pad.counter path = tf.path pry_eval "edit #{path}" Pad.counter.should == counter + 1 end end it "should not reload the file if it is not a ruby file" do temp_file('.py') do |tf| counter = Pad.counter path = tf.path pry_eval "edit #{path}" Pad.counter.should == counter end end it "should not reload a ruby file if -n is given" do temp_file do |tf| counter = Pad.counter path = tf.path Pad.counter.should == counter end end it "should reload a non-ruby file if -r is given" do temp_file('.pryrc') do |tf| counter = Pad.counter path = tf.path pry_eval "edit -r #{path}" Pad.counter.should == counter + 1 end end end describe do before do @reloading = nil Pry.config.editor = lambda do |file, line, reloading| @file = file; @line = line; @reloading = reloading nil end end it "should pass the editor a reloading arg" do pry_eval 'edit lib/pry.rb' @reloading.should == true pry_eval 'edit -n lib/pry.rb' @reloading.should == false end end end describe "with --ex" do before do @t = pry_tester do def last_exception=(exception) @pry.last_exception = exception end def last_exception; @pry.last_exception; end end end describe "with a real file" do before do @tf = Tempfile.new(["pry", ".rb"]) @path = @tf.path @tf << "1\n2\nraise RuntimeError" @tf.flush begin load @path rescue RuntimeError => e @t.last_exception = e end end after do @tf.close(true) File.unlink("#{@path}c") if File.exists?("#{@path}c") #rbx end it "should reload the file" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "FOO = 'BAR'" } if defined?(Rubinius::Compiler) File.unlink Rubinius::Compiler.compiled_name file end nil } defined?(FOO).should.be.nil @t.eval 'edit --ex' FOO.should == 'BAR' end # regression test (this used to edit the current method instead # of the exception) it 'edits the exception even when in a patched method context' do source_location = nil Pry.config.editor = lambda {|file, line| source_location = [file, line] nil } Pad.le = @t.last_exception redirect_pry_io(InputTester.new("def broken_method", "binding.pry", "end", "broken_method", "_pry_.last_exception = Pad.le", "edit --ex -n", "exit-all", "exit-all")) do Object.new.pry end source_location.should == [@path, 3] Pad.clear end it "should not reload the file if -n is passed" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "FOO2 = 'BAZ'" } nil } defined?(FOO2).should.be.nil @t.eval 'edit -n --ex' defined?(FOO2).should.be.nil end describe "with --patch" do # Original source code must be untouched. it "should apply changes only in memory (monkey patching)" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "FOO3 = 'PIYO'" } @patched_def = File.open(file, 'r').read nil } defined?(FOO3).should.be.nil @t.eval 'edit --ex --patch' FOO3.should == 'PIYO' @tf.rewind @tf.read.should == "1\n2\nraise RuntimeError" @patched_def.should == "FOO3 = 'PIYO'" end end end describe "with --ex NUM" do before do Pry.config.editor = proc do |file, line| @__ex_file__ = file @__ex_line__ = line nil end @t.last_exception = mock_exception('a:1', 'b:2', 'c:3') end it 'should start on first level of backtrace with just --ex' do @t.eval 'edit -n --ex' @__ex_file__.should == "a" @__ex_line__.should == 1 end it 'should start editor on first level of backtrace with --ex 0' do @t.eval 'edit -n --ex 0' @__ex_file__.should == "a" @__ex_line__.should == 1 end it 'should start editor on second level of backtrace with --ex 1' do @t.eval 'edit -n --ex 1' @__ex_file__.should == "b" @__ex_line__.should == 2 end it 'should start editor on third level of backtrace with --ex 2' do @t.eval 'edit -n --ex 2' @__ex_file__.should == "c" @__ex_line__.should == 3 end it 'should display error message when backtrace level is invalid' do proc { @t.eval 'edit -n --ex 4' }.should.raise(Pry::CommandError) end end end describe "without FILE" do before do @t = pry_tester end it "should edit the current expression if it's incomplete" do @t.push 'def a' @t.process_command 'edit' @contents.should == "def a\n" end it "should edit the previous expression if the current is empty" do @t.eval 'def a; 2; end', 'edit' @contents.should == "def a; 2; end\n" end it "should use a blank file if -t is specified" do @t.eval 'def a; 5; end', 'edit -t' @contents.should == "\n" end it "should use a blank file if -t given, even during an expression" do @t.push 'def a;' @t.process_command 'edit -t' @contents.should == "\n" end it "should position the cursor at the end of the expression" do @t.eval "def a; 2;\nend" @t.process_command 'edit' @line.should == 2 end it "should evaluate the expression" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "'FOO'\n" } nil } @t.process_command 'edit' @t.eval_string.should == "'FOO'\n" end it "should ignore -n for tempfiles" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "'FOO'\n" } nil } @t.process_command "edit -n" @t.eval_string.should == "'FOO'\n" end it "should not evaluate a file with -n" do Pry.config.editor = lambda {|file, line| File.open(file, 'w'){|f| f << "'FOO'\n" } nil } begin @t.process_command 'edit -n spec/fixtures/foo.rb' File.read("spec/fixtures/foo.rb").should == "'FOO'\n" @t.eval_string.should == '' ensure FileUtils.rm "spec/fixtures/foo.rb" end end end describe "with --in" do it "should edit the nth line of _in_" do pry_eval '10', '11', 'edit --in -2' @contents.should == "10\n" end it "should edit the last line if no argument is given" do pry_eval '10', '11', 'edit --in' @contents.should == "11\n" end it "should edit a range of lines if a range is given" do pry_eval "10", "11", "edit -i 1,2" @contents.should == "10\n11\n" end it "should edit a multi-line expression as it occupies one line of _in_" do pry_eval "class Fixnum\n def invert; -self; end\nend", "edit -i 1" @contents.should == "class Fixnum\n def invert; -self; end\nend\n" end it "should not work with a filename" do proc { pry_eval 'edit ruby.rb -i' }.should.raise(Pry::CommandError). message.should =~ /Only one of --ex, --temp, --in, --method and FILE/ end it "should not work with nonsense" do proc { pry_eval 'edit --in three' }.should.raise(Pry::CommandError). message.should =~ /Not a valid range: three/ end end describe 'when editing a method by name' do it 'uses patch editing on methods that were previously patched' do # initial definition filename = __FILE__ line = __LINE__ + 2 klass = Class.new do def to_be_edited; 1; end end method1 = klass.instance_method(:to_be_edited) # now patch it Pry::Method(method1).redefine('def to_be_edited; 2; end') method2 = klass.instance_method(:to_be_edited) # edit by name, no --patch tester = pry_tester binding tester.pry.config.editor = lambda do |filename, line| File.open(filename, 'w') { |f| f.write 'def to_be_edited; 3; end' } nil end tester.eval "edit klass#to_be_edited" method3 = klass.instance_method(:to_be_edited) # methods all worked as expected method1.bind(klass.new).call.should == 1 method2.bind(klass.new).call.should == 2 method3.bind(klass.new).call.should == 3 # original file is unchanged File.readlines(filename)[line-1].strip.should == 'def to_be_edited; 1; end' end # it 'uses runtime editing on methods that were defined in the console' end describe "old edit-method tests now migrated to edit" do describe "on a method defined in a file" do before do @tempfile = (Tempfile.new(['pry', '.rb'])) @tempfile.puts <<-EOS module A def a :yup end def b :kinda end end class X include A def self.x :double_yup end def x :nope end def b super end alias c b def y? :because end class B G = :nawt def foo :possibly G end end end EOS @tempfile.flush load @tempfile.path @tempfile_path = @tempfile.path end after do @tempfile.close(true) end describe 'without -p' do before do @file = @line = @contents = nil Pry.config.editor = lambda do |file, line| @file = file; @line = line nil end end it "should correctly find a class method" do pry_eval 'edit X.x' @file.should == @tempfile_path @line.should == 14 end it "should correctly find an instance method" do pry_eval 'edit X#x' @file.should == @tempfile_path @line.should == 18 end it "should correctly find a method on an instance" do pry_eval 'x = X.new', 'edit x.x' @file.should == @tempfile_path @line.should == 18 end it "should correctly find a method from a module" do pry_eval 'edit X#a' @file.should == @tempfile_path @line.should == 2 end it "should correctly find an aliased method" do pry_eval 'edit X#c' @file.should == @tempfile_path @line.should == 22 end end describe 'with -p' do before do Pry.config.editor = lambda do |file, line| lines = File.read(file).lines.to_a lines[1] = ":maybe\n" File.open(file, 'w') do |f| f.write(lines.join) end @patched_def = String(lines[1]).chomp nil end end it "should successfully replace a class method" do pry_eval 'edit -p X.x' class << X X.method(:x).owner.should == self end X.method(:x).receiver.should == X X.x.should == :maybe end it "should successfully replace an instance method" do pry_eval 'edit -p X#x' X.instance_method(:x).owner.should == X X.new.x.should == :maybe end it "should successfully replace a method on an instance" do pry_eval 'instance = X.new', 'edit -p instance.x' instance = X.new instance.method(:x).owner.should == X instance.x.should == :maybe end it "should successfully replace a method from a module" do pry_eval 'edit -p X#a' X.instance_method(:a).owner.should == A X.new.a.should == :maybe end it "should successfully replace a method with a question mark" do pry_eval 'edit -p X#y?' X.instance_method(:y?).owner.should == X X.new.y?.should == :maybe end it "should preserve module nesting" do pry_eval 'edit -p X::B#foo' X::B.instance_method(:foo).owner.should == X::B X::B.new.foo.should == :nawt end describe "monkey-patching" do before do @edit = 'edit --patch ' # A shortcut. end # @param [Integer] lineno # @return [String] the stripped line from the tempfile at +lineno+ def stripped_line_at(lineno) @tempfile.rewind @tempfile.lines.to_a[lineno].strip end # Applies the monkey patch for +method+ with help of evaluation of # +eval_strs+. The idea is to capture the initial line number (before # the monkey patch), because it gets overwritten by the line number from # the monkey patch. And our goal is to check that the original # definition hasn't changed. # @param [UnboundMethod] method # @param [Array] eval_strs # @return [Array