# frozen_string_literal: true require 'tempfile' require 'rbconfig' RSpec.describe Pry::History do let(:history) { Pry::History.new } before do Pry.history = history @saved_history = "1\n2\n3\ninvalid\0 line\n" Pry.history.loader = proc do |&blk| @saved_history.lines.each { |l| blk.call(l) } end Pry.load_history end after do Pry.history.clear Pry.history.instance_variable_set(:@original_lines, 0) end describe ".default_file" do let(:xdg_name) { 'XDG_DATA_HOME' } let(:default_path) { File.expand_path '~/.pry_history' } def stub_hist(options) has_default = options.fetch :has_default xdg_home = options.fetch :xdg_home allow(File).to receive(:exist?) # there's a test helper hook that hits this allow(File).to receive(:exist?).with(default_path).and_return(has_default) allow(ENV).to receive(:[]) allow(ENV).to receive(:key?) allow(ENV).to receive(:[]).with(xdg_name).and_return(xdg_home) allow(ENV).to receive(:key?).with(xdg_name).and_return(!!xdg_home) end it "returns ~/.local/share/pry/pry_history" do stub_hist has_default: false, xdg_home: nil expect(described_class.default_file).to match('/.local/share/pry/pry_history') end context "when ~/.pry_history exists" do it "returns ~/.pry_history" do stub_hist has_default: true, xdg_home: nil expect(described_class.default_file).to eq default_path end end context "when $XDG_DATA_HOME is defined" do let(:expected_file_path) { File.expand_path('/my/path/pry/pry_history') } it "returns config location relative to $XDG_DATA_HOME" do stub_hist has_default: false, xdg_home: '/my/path' expect(described_class.default_file).to eq(expected_file_path) end it "returns config location relative to $XDG_DATA_HOME when ~/.pryrc exists" do stub_hist has_default: true, xdg_home: '/my/path' expect(described_class.default_file).to eq(expected_file_path) end end end describe '#push' do it "does not record duplicated lines" do Pry.history << '3' Pry.history << '_ += 1' Pry.history << '_ += 1' expect(Pry.history.to_a.grep('_ += 1').count).to eq 1 end it "does not record lines that contain a NULL byte" do c = Pry.history.to_a.size Pry.history << "a\0b" expect(Pry.history.to_a.size).to eq c end it "does not record empty lines" do c = Pry.history.to_a.size Pry.history << '' expect(Pry.history.to_a.size).to eq c end end describe "#clear" do before do @old_file = Pry.config.history_file @hist_file_path = File.expand_path('spec/fixtures/pry_history') Pry.config.history_file = @hist_file_path Pry.history.clear Pry.load_history end after do Pry.config.history_file = @old_file end it "clears this session's history" do expect(Pry.history.to_a.size).to be > 0 Pry.history.clear expect(Pry.history.to_a.size).to eq 0 expect(Pry.history.original_lines).to eq 0 end it "doesn't affect the contents of the history file" do expect(Pry.history.to_a.size).to eq 3 Pry.history.clear File.open(@hist_file_path, 'r') do |fh| file = fh.to_a expect(file.length).to eq 3 expect(file.any? { |a| a =~ /athos/ }).to eq true end end end describe "#history_line_count" do it "counts entries in history" do Pry.history.clear saved_history = "olgierd\ngustlik\njanek\ngrzes\ntomek\n" Pry.history.loader = proc do |&blk| saved_history.lines.each { |l| blk.call(l) } end Pry.load_history expect(Pry.history.history_line_count).to eq 5 end end describe "#session_line_count" do it "returns the number of lines in history from just this session" do Pry.history << 'you?' Pry.history << 'you are so precious' expect(Pry.history.session_line_count).to eq 2 end end describe ".load_history" do it "reads the contents of the file" do expect(Pry.history.to_a[-2..-1]).to eq %w[2 3] end end describe "saving to a file" do before do @histfile = Tempfile.new(%w[pryhistory txt]) @history = Pry::History.new(file_path: @histfile.path) Pry.config.history_save = true end after do @histfile.close(true) Pry.config.history_save = false end it "saves lines to a file as they are written" do @history.push "5" expect(File.read(@histfile.path)).to eq "5\n" end it "interleaves lines from many places" do @history.push "5" File.open(@histfile.path, 'a') { |f| f.puts "6" } @history.push "7" expect(File.read(@histfile.path)).to eq "5\n6\n7\n" end it "should not write histignore words to the history file" do Pry.config.history_ignorelist = ["ls", /hist*/, 'exit'] @history.push "ls" @history.push "hist" @history.push "kakaroto" @history.push "exit" expect(File.open(@histfile.path).entries.size).to eq 1 expect(IO.readlines(@histfile.path).first).to eq "kakaroto\n" end end describe "expanding the history file path" do before { Pry.config.history_save = true } after { Pry.config.history_save = false } it "recognizes ~ (#1262)" do # This is a pretty dumb way of testing this, but at least it shouldn't # succeed spuriously. history = Pry::History.new(file_path: '~/test_history') error = Class.new(RuntimeError) expected_path = File.expand_path(File.join(ENV['HOME'].to_s, "/test_history")) expect(File).to receive(:open) .with(expected_path, 'a', 0o600) .and_raise(error) expect { history.push 'a line' }.to raise_error error end end describe "file io errors" do let(:custom_history) { Pry::History.new(file_path: file_path) } let(:file_path) { Tempfile.new("pry_history_spec").path } [Errno::EACCES, Errno::ENOENT].each do |error_class| it "handles #{error_class} failure to read from history" do expect(File).to receive(:foreach).and_raise(error_class) expect(custom_history).to receive(:warn).with(/Unable to read history file:/) expect { custom_history.load }.to_not raise_error end it "handles #{error_class} failure to write history" do Pry.config.history_save = true expect(File).to receive(:open).with(file_path, "a", 0o600).and_raise(error_class) expect(custom_history).to receive(:warn).with(/Unable to write history file:/) expect { custom_history.push("anything") }.to_not raise_error end end end end