From 77295853bfdd311239e4ff31af9436f8756a5489 Mon Sep 17 00:00:00 2001 From: Kyrylo Silin Date: Thu, 14 Mar 2019 00:48:33 +0200 Subject: [PATCH] cli_spec: improve test coverage and refactor 57.14% is covered. We don't cover `on` switches because it's hard to test them. We also replace `exit` with `Kernel.exit` because this way we can mock this method. Not sure why plain `exit` cannot be mocked. --- lib/pry/cli.rb | 10 +- spec/cli_spec.rb | 281 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 221 insertions(+), 70 deletions(-) diff --git a/lib/pry/cli.rb b/lib/pry/cli.rb index 4da8d350..32f035b2 100644 --- a/lib/pry/cli.rb +++ b/lib/pry/cli.rb @@ -80,7 +80,7 @@ class Pry rescue Pry::Slop::InvalidOptionError # Display help message on unknown switches and exit. puts Pry::Slop.new(&options) - exit + Kernel.exit end Pry.initial_session_setup @@ -93,7 +93,7 @@ class Pry end def start(opts) - exit if opts.help? + Kernel.exit if opts.help? # invoked via cli Pry.cli = true @@ -110,7 +110,7 @@ class Pry if !@pass_argv && Pry::CLI.input_args.any? && Pry::CLI.input_args != ["pry"] full_name = File.expand_path(Pry::CLI.input_args.first) Pry.load_file_through_repl(full_name) - exit + Kernel.exit end # Start the session (running any code passed with -e, if there is any) @@ -182,7 +182,7 @@ Pry::CLI.add_options do Pry.locate_plugins.each do |plugin| puts plugin.name.to_s.ljust(18) << plugin.spec.summary end - exit + Kernel.exit end on "simple-prompt", "Enable simple prompt mode" do @@ -214,7 +214,7 @@ Pry::CLI.add_options do on :v, :version, "Display the Pry version" do puts "Pry version #{Pry::VERSION} on Ruby #{RUBY_VERSION}" - exit + Kernel.exit end on :c, :context=, diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index 949d00b8..b9c8ee46 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -1,85 +1,236 @@ RSpec.describe Pry::CLI do - before do - Pry::CLI.reset - end + before { described_class.reset } - describe "parsing options" do - it 'should raise if no options defined' do - expect { Pry::CLI.parse_options(["--nothing"]) } - .to raise_error Pry::CLI::NoOptionsError + describe ".add_options" do + it "returns self" do + expect(described_class.add_options).to eq(described_class) end - it "should remove args from ARGV by default" do - argv = ['filename', '-v'] - Pry::CLI.add_options do - on :v, "Display the Pry version" do - # irrelevant - end - end.parse_options(argv) - expect(argv.include?('-v')).to eq false + context "when options is nil and a block is provided" do + before { described_class.options = nil } + + it "sets the block as options" do + block = proc {} + described_class.add_options(&block) + expect(described_class.options).to eql(block) + end + end + + context "when options were previously set" do + it "overwrites the options proc that executes the provided block" do + described_class.options = proc {} + + executed = false + described_class.add_options { executed = true } + + described_class.options.call + expect(executed).to be_truthy + end + + it "overwrites the options proc that executes original options" do + original_executed = false + described_class.options = proc { original_executed = true } + + described_class.add_options {} + described_class.options.call + + expect(original_executed).to be_truthy + end end end - describe "adding options" do - it "should be able to add an option" do - run = false - - Pry::CLI.add_options do - on :optiontest, "A test option" do - run = true - end - end.parse_options(["--optiontest"]) - - expect(run).to eq true + describe ".add_plugin_options" do + it "returns self" do + expect(described_class.add_plugin_options).to eq(described_class) end - it "should be able to add multiple options" do - run = false - run2 = false + it "loads cli options of plugins" do + plugin_mock = double + expect(plugin_mock).to receive(:load_cli_options) + plugins = { 'pry-testplugin' => plugin_mock } + expect(Pry).to receive(:plugins).and_return(plugins) - Pry::CLI.add_options do - on :optiontest, "A test option" do - run = true - end - end.add_options do - on :optiontest2, "Another test option" do - run2 = true - end - end.parse_options(["--optiontest", "--optiontest2"]) - - expect(run).to equal true - expect(run2).to equal true + described_class.add_plugin_options end end - describe "processing options" do - it "should be able to process an option" do - run = false - - Pry::CLI.add_options do - on :optiontest, "A test option" - end.add_option_processor do |opts| - run = true if opts.present?(:optiontest) - end.parse_options(["--optiontest"]) - - expect(run).to eq true + describe ".add_option_processor" do + it "returns self" do + expect(described_class.add_option_processor {}).to eq(described_class) end - it "should be able to process multiple options" do - run = false - run2 = false + it "adds an option processor" do + option_processor = proc {} + described_class.add_option_processor(&option_processor) + expect(described_class.option_processors).to eql([option_processor]) + end + end - Pry::CLI.add_options do - on :optiontest, "A test option" - on :optiontest2, "Another test option" - end.add_option_processor do |opts| - run = true if opts.present?(:optiontest) - end.add_option_processor do |opts| - run2 = true if opts.present?(:optiontest2) - end.parse_options(["--optiontest", "--optiontest2"]) + describe ".parse_options" do + context "when option exists" do + before { described_class.options = proc { on(:v, 'test') } } - expect(run).to eq true - expect(run2).to eq true + it "removes the existing option from ARGV" do + argv = %w[filename -v] + described_class.parse_options(argv) + expect(argv).not_to include('-v') + end + + it "initializes session setup" do + expect(Pry).to receive(:initial_session_setup) + described_class.parse_options(%w[-v]) + end + + it "finalizes session setup" do + expect(Pry).to receive(:final_session_setup) + described_class.parse_options(%w[-v]) + end + end + + context "when multiple options exist" do + it "processes only called options" do + processor_a_called = false + processor_b_called = false + processor_c_called = false + described_class.options = proc do + on('option-a', 'test a') { processor_a_called = true } + on('option-b', 'test b') { processor_b_called = true } + on('option-c', 'test c') { processor_c_called = true } + end + + described_class.parse_options(%w[--option-a --option-b]) + + expect(processor_a_called).to be_truthy + expect(processor_b_called).to be_truthy + expect(processor_c_called).to be_falsey + end + end + + context "when option doesn't exist" do + it "raises error" do + expect { described_class.parse_options(['--nothing']) } + .to raise_error(Pry::CLI::NoOptionsError) + end + end + + context "when argv is passed with a dash (-)" do + before { described_class.options = proc {} } + + it "sets everything after the dash as input args" do + argv = %w[filename - foo bar] + described_class.parse_options(argv) + expect(described_class.input_args).to eq(%w[foo bar]) + end + end + + context "when argv is passed with a double dash (--)" do + before { described_class.options = proc {} } + + it "sets everything after the double dash as input args" do + argv = %w[filename -- foo bar] + described_class.parse_options(argv) + expect(described_class.input_args).to eq(%w[foo bar]) + end + end + + context "when invalid option is provided" do + before { described_class.options = proc { on(:valid, 'valid') } } + + it "exits program" do + expect(Kernel).to receive(:exit) + expect(STDOUT).to receive(:puts) + + described_class.parse_options(%w[--invalid]) + end + end + end + + describe ".start" do + before do + # Don't start Pry session in the middle of tests. + allow(Pry).to receive(:start) + + described_class.options = proc {} + end + + it "sets Pry.cli to true" do + opts = described_class.parse_options(%w[]) + described_class.start(opts) + expect(Pry.cli).to be_truthy + end + + context "when the help option is provided" do + before { described_class.options = proc { on(:help, 'help') } } + + it "exits" do + expect(Kernel).to receive(:exit) + + opts = described_class.parse_options(%w[--help]) + described_class.start(opts) + end + end + + context "when the context option is provided" do + before { described_class.options = proc { on(:context=, 'context') } } + + it "initializes session setup" do + expect(Pry).to receive(:initial_session_setup).twice + opts = described_class.parse_options(%w[--context=Object]) + described_class.start(opts) + end + + it "finalizes session setup" do + expect(Pry).to receive(:final_session_setup).twice + opts = described_class.parse_options(%w[--context=Object]) + described_class.start(opts) + end + + it "starts Pry in the provided context" do + expect(Pry).to receive(:start).with( + instance_of(Binding), input: instance_of(StringIO) + ) do |binding, _opts| + expect(binding.eval('self')).to be_an(Object) + end + opts = described_class.parse_options(%w[--context=Object]) + described_class.start(opts) + end + end + + context "when the context option is not provided" do + before { described_class.options = proc {} } + + it "starts Pry in the top level" do + expect(Pry).to receive(:start).with( + instance_of(Binding), input: instance_of(StringIO) + ) do |binding, _opts| + expect(binding.eval('self')).to eq(Pry.main) + end + opts = described_class.parse_options(%w[]) + described_class.start(opts) + end + end + + context "when there are some input args" do + before { described_class.options = proc {} } + + it "loads files through repl and exits" do + expect(Pry).to receive(:load_file_through_repl).with(match(%r{pry/foo})) + expect(Kernel).to receive(:exit) + + opts = described_class.parse_options(%w[foo]) + described_class.start(opts) + end + end + + context "when 'pry' is passed as an input arg" do + before { described_class.options = proc {} } + + it "does not load files through repl" do + expect(Pry).not_to receive(:load_file_through_repl) + + opts = described_class.parse_options(%w[pry]) + described_class.start(opts) + end end end end