From 59c8d50653480bef3f24517296e6ddf937fdf6bc Mon Sep 17 00:00:00 2001 From: hsbt Date: Fri, 2 Nov 2018 23:07:56 +0000 Subject: Added bundler as default gems. Revisit [Feature #12733] * bin/*, lib/bundler/*, lib/bundler.rb, spec/bundler, man/*: Merge from latest stable branch of bundler/bundler repository and added workaround patches. I will backport them into upstream. * common.mk, defs/gmake.mk: Added `test-bundler` task for test suite of bundler. * tool/sync_default_gems.rb: Added sync task for bundler. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65509 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- spec/bundler/commands/add_spec.rb | 217 ++++++++ spec/bundler/commands/binstubs_spec.rb | 453 ++++++++++++++++ spec/bundler/commands/check_spec.rb | 354 +++++++++++++ spec/bundler/commands/clean_spec.rb | 771 +++++++++++++++++++++++++++ spec/bundler/commands/config_spec.rb | 384 ++++++++++++++ spec/bundler/commands/console_spec.rb | 106 ++++ spec/bundler/commands/doctor_spec.rb | 110 ++++ spec/bundler/commands/exec_spec.rb | 858 ++++++++++++++++++++++++++++++ spec/bundler/commands/help_spec.rb | 98 ++++ spec/bundler/commands/info_spec.rb | 57 ++ spec/bundler/commands/init_spec.rb | 181 +++++++ spec/bundler/commands/inject_spec.rb | 117 ++++ spec/bundler/commands/install_spec.rb | 587 ++++++++++++++++++++ spec/bundler/commands/issue_spec.rb | 16 + spec/bundler/commands/licenses_spec.rb | 38 ++ spec/bundler/commands/list_spec.rb | 131 +++++ spec/bundler/commands/lock_spec.rb | 322 +++++++++++ spec/bundler/commands/newgem_spec.rb | 912 +++++++++++++++++++++++++++++++ spec/bundler/commands/open_spec.rb | 92 ++++ spec/bundler/commands/outdated_spec.rb | 782 +++++++++++++++++++++++++++ spec/bundler/commands/package_spec.rb | 306 +++++++++++ spec/bundler/commands/pristine_spec.rb | 192 +++++++ spec/bundler/commands/remove_spec.rb | 583 ++++++++++++++++++++ spec/bundler/commands/show_spec.rb | 244 +++++++++ spec/bundler/commands/update_spec.rb | 943 +++++++++++++++++++++++++++++++++ spec/bundler/commands/version_spec.rb | 39 ++ spec/bundler/commands/viz_spec.rb | 149 ++++++ 27 files changed, 9042 insertions(+) create mode 100644 spec/bundler/commands/add_spec.rb create mode 100644 spec/bundler/commands/binstubs_spec.rb create mode 100644 spec/bundler/commands/check_spec.rb create mode 100644 spec/bundler/commands/clean_spec.rb create mode 100644 spec/bundler/commands/config_spec.rb create mode 100644 spec/bundler/commands/console_spec.rb create mode 100644 spec/bundler/commands/doctor_spec.rb create mode 100644 spec/bundler/commands/exec_spec.rb create mode 100644 spec/bundler/commands/help_spec.rb create mode 100644 spec/bundler/commands/info_spec.rb create mode 100644 spec/bundler/commands/init_spec.rb create mode 100644 spec/bundler/commands/inject_spec.rb create mode 100644 spec/bundler/commands/install_spec.rb create mode 100644 spec/bundler/commands/issue_spec.rb create mode 100644 spec/bundler/commands/licenses_spec.rb create mode 100644 spec/bundler/commands/list_spec.rb create mode 100644 spec/bundler/commands/lock_spec.rb create mode 100644 spec/bundler/commands/newgem_spec.rb create mode 100644 spec/bundler/commands/open_spec.rb create mode 100644 spec/bundler/commands/outdated_spec.rb create mode 100644 spec/bundler/commands/package_spec.rb create mode 100644 spec/bundler/commands/pristine_spec.rb create mode 100644 spec/bundler/commands/remove_spec.rb create mode 100644 spec/bundler/commands/show_spec.rb create mode 100644 spec/bundler/commands/update_spec.rb create mode 100644 spec/bundler/commands/version_spec.rb create mode 100644 spec/bundler/commands/viz_spec.rb (limited to 'spec/bundler/commands') diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb new file mode 100644 index 0000000000..9f11adbcf8 --- /dev/null +++ b/spec/bundler/commands/add_spec.rb @@ -0,0 +1,217 @@ +# frozen_string_literal: true + +RSpec.describe "bundle add" do + before :each do + build_repo2 do + build_gem "foo", "1.1" + build_gem "foo", "2.0" + build_gem "baz", "1.2.3" + build_gem "bar", "0.12.3" + build_gem "cat", "0.12.3.pre" + build_gem "dog", "1.1.3.pre" + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "weakling", "~> 0.0.1" + G + end + + context "when no gems are specified" do + it "shows error" do + bundle "add" + + expect(last_command.bundler_err).to include("Please specify gems to add") + end + end + + describe "without version specified" do + it "version requirement becomes ~> major.minor.patch when resolved version is < 1.0" do + bundle "add 'bar'" + expect(bundled_app("Gemfile").read).to match(/gem "bar", "~> 0.12.3"/) + expect(the_bundle).to include_gems "bar 0.12.3" + end + + it "version requirement becomes ~> major.minor when resolved version is > 1.0" do + bundle "add 'baz'" + expect(bundled_app("Gemfile").read).to match(/gem "baz", "~> 1.2"/) + expect(the_bundle).to include_gems "baz 1.2.3" + end + + it "version requirement becomes ~> major.minor.patch.pre when resolved version is < 1.0" do + bundle "add 'cat'" + expect(bundled_app("Gemfile").read).to match(/gem "cat", "~> 0.12.3.pre"/) + expect(the_bundle).to include_gems "cat 0.12.3.pre" + end + + it "version requirement becomes ~> major.minor.pre when resolved version is > 1.0.pre" do + bundle "add 'dog'" + expect(bundled_app("Gemfile").read).to match(/gem "dog", "~> 1.1.pre"/) + expect(the_bundle).to include_gems "dog 1.1.3.pre" + end + end + + describe "with --version" do + it "adds dependency of specified version and runs install" do + bundle "add 'foo' --version='~> 1.0'" + expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 1.0"/) + expect(the_bundle).to include_gems "foo 1.1" + end + + it "adds multiple version constraints when specified" do + requirements = ["< 3.0", "> 1.0"] + bundle "add 'foo' --version='#{requirements.join(", ")}'" + expect(bundled_app("Gemfile").read).to match(/gem "foo", #{Gem::Requirement.new(requirements).as_list.map(&:dump).join(', ')}/) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --group" do + it "adds dependency for the specified group" do + bundle "add 'foo' --group='development'" + expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 2.0", :group => :development/) + expect(the_bundle).to include_gems "foo 2.0" + end + + it "adds dependency to more than one group" do + bundle "add 'foo' --group='development, test'" + expect(bundled_app("Gemfile").read).to match(/gem "foo", "~> 2.0", :groups => \[:development, :test\]/) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --source" do + it "adds dependency with specified source" do + bundle "add 'foo' --source='file://#{gem_repo2}'" + + expect(bundled_app("Gemfile").read).to match(%r{gem "foo", "~> 2.0", :source => "file:\/\/#{gem_repo2}"}) + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --skip-install" do + it "adds gem to Gemfile but is not installed" do + bundle "add foo --skip-install --version=2.0" + + expect(bundled_app("Gemfile").read).to match(/gem "foo", "= 2.0"/) + expect(the_bundle).to_not include_gems "foo 2.0" + end + end + + it "using combination of short form options works like long form" do + bundle "add 'foo' -s='file://#{gem_repo2}' -g='development' -v='~>1.0'" + expect(bundled_app("Gemfile").read).to include %(gem "foo", "~> 1.0", :group => :development, :source => "file://#{gem_repo2}") + expect(the_bundle).to include_gems "foo 1.1" + end + + it "shows error message when version is not formatted correctly" do + bundle "add 'foo' -v='~>1 . 0'" + expect(out).to match("Invalid gem requirement pattern '~>1 . 0'") + end + + it "shows error message when gem cannot be found" do + bundle "add 'werk_it'" + expect(out).to match("Could not find gem 'werk_it' in") + + bundle "add 'werk_it' -s='file://#{gem_repo2}'" + expect(out).to match("Could not find gem 'werk_it' in rubygems repository") + end + + it "shows error message when source cannot be reached" do + bundle "add 'baz' --source='http://badhostasdf'" + expect(out).to include("Could not reach host badhostasdf. Check your network connection and try again.") + + bundle "add 'baz' --source='file://does/not/exist'" + expect(out).to include("Could not fetch specs from file://does/not/exist/") + end + + describe "with --optimistic" do + it "adds optimistic version" do + bundle! "add 'foo' --optimistic" + expect(bundled_app("Gemfile").read).to include %(gem "foo", ">= 2.0") + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --strict option" do + it "adds strict version" do + bundle! "add 'foo' --strict" + expect(bundled_app("Gemfile").read).to include %(gem "foo", "= 2.0") + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with no option" do + it "adds pessimistic version" do + bundle! "add 'foo'" + expect(bundled_app("Gemfile").read).to include %(gem "foo", "~> 2.0") + expect(the_bundle).to include_gems "foo 2.0" + end + end + + describe "with --optimistic and --strict" do + it "throws error" do + bundle "add 'foo' --strict --optimistic" + + expect(out).to include("You can not specify `--strict` and `--optimistic` at the same time") + end + end + + context "multiple gems" do + it "adds multiple gems to gemfile" do + bundle! "add bar baz" + + expect(bundled_app("Gemfile").read).to match(/gem "bar", "~> 0.12.3"/) + expect(bundled_app("Gemfile").read).to match(/gem "baz", "~> 1.2"/) + end + + it "throws error if any of the specified gems are present in the gemfile with different version" do + bundle "add weakling bar" + + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).to include("You specified: weakling (~> 0.0.1) and weakling (>= 0).") + end + end + + describe "when a gem is added which is already specified in Gemfile with version" do + it "shows an error when added with different version requirement" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack", "1.0" + G + + bundle "add 'rack' --version=1.1" + + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") + end + + it "shows error when added without version requirements" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack", "1.0" + G + + bundle "add 'rack'" + + expect(out).to include("Gem already added.") + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).not_to include("If you want to update the gem version, run `bundle update rack`. You may also need to change the version requirement specified in the Gemfile if it's too restrictive") + end + end + + describe "when a gem is added which is already specified in Gemfile without version" do + it "shows an error when added with different version requirement" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + G + + bundle "add 'rack' --version=1.1" + + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).to include("If you want to update the gem version, run `bundle update rack`.") + expect(out).not_to include("You may also need to change the version requirement specified in the Gemfile if it's too restrictive") + end + end +end diff --git a/spec/bundler/commands/binstubs_spec.rb b/spec/bundler/commands/binstubs_spec.rb new file mode 100644 index 0000000000..6a705d3423 --- /dev/null +++ b/spec/bundler/commands/binstubs_spec.rb @@ -0,0 +1,453 @@ +# frozen_string_literal: true + +RSpec.describe "bundle binstubs " do + context "when the gem exists in the lockfile" do + it "sets up the binstub" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack" + + expect(bundled_app("bin/rackup")).to exist + end + + it "does not install other binstubs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rails" + G + + bundle "binstubs rails" + + expect(bundled_app("bin/rackup")).not_to exist + expect(bundled_app("bin/rails")).to exist + end + + it "does install multiple binstubs" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rails" + G + + bundle "binstubs rails rack" + + expect(bundled_app("bin/rackup")).to exist + expect(bundled_app("bin/rails")).to exist + end + + it "allows installing all binstubs" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle! :binstubs, :all => true + + expect(bundled_app("bin/rails")).to exist + expect(bundled_app("bin/rake")).to exist + end + + it "displays an error when used without any gem" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs" + expect(exitstatus).to eq(1) if exitstatus + expect(out).to include("`bundle binstubs` needs at least one gem to run.") + end + + it "displays an error when used with --all and gems" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack", :all => true + expect(last_command).to be_failure + expect(last_command.bundler_err).to include("Cannot specify --all with specific gems") + end + + context "when generating bundle binstub outside bundler" do + it "should abort" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack" + + File.open("bin/bundle", "wb") do |file| + file.print "OMG" + end + + sys_exec "bin/rackup" + + expect(last_command.stderr).to include("was not generated by Bundler") + end + end + + context "the bundle binstub" do + before do + if system_bundler_version == :bundler + system_gems :bundler + elsif system_bundler_version + build_repo4 do + build_gem "bundler", system_bundler_version do |s| + s.executables = "bundle" + s.bindir = "exe" + s.write "exe/bundle", "puts %(system bundler #{system_bundler_version}\\n\#{ARGV.inspect})" + end + end + system_gems "bundler-#{system_bundler_version}", :gem_repo => gem_repo4 + end + build_repo2 do + build_gem "prints_loaded_gems", "1.0" do |s| + s.executables = "print_loaded_gems" + s.bindir = "exe" + s.write "exe/print_loaded_gems", <<-R + specs = Gem.loaded_specs.values.reject {|s| Bundler.rubygems.spec_default_gem?(s) } + puts specs.map(&:full_name).sort.inspect + R + end + end + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rack" + gem "prints_loaded_gems" + G + bundle! "binstubs bundler rack prints_loaded_gems" + end + + # When environment has a same version of bundler as default gems. + # `system_gems "bundler-x.y.z"` will detect system binstub. + # We need to avoid it by virtual version of bundler. + let(:system_bundler_version) { Gem::Version.new(Bundler::VERSION).bump.to_s } + + context "when system bundler was used" do + # Support master branch of bundler + if ENV["BUNDLER_SPEC_SUB_VERSION"] + let(:system_bundler_version) { Bundler::VERSION } + end + it "runs bundler" do + sys_exec! "#{bundled_app("bin/bundle")} install" + expect(out).to eq %(system bundler #{system_bundler_version}\n["install"]) + end + end + + context "when BUNDLER_VERSION is set" do + let(:system_bundler_version) { Bundler::VERSION } + + it "runs the correct version of bundler" do + sys_exec "BUNDLER_VERSION='999.999.999' #{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end + + context "when a lockfile exists with a locked bundler version" do + let(:system_bundler_version) { Bundler::VERSION } + + it "runs the correct version of bundler when the version is newer" do + lockfile lockfile.gsub(system_bundler_version, "999.999.999") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + + it "runs the correct version of bundler when the version is older" do + simulate_bundler_version "55" + lockfile lockfile.gsub(system_bundler_version, "44.0") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (44.0) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '44.0'`") + end + + it "runs the correct version of bundler when the version is a pre-release" do + simulate_bundler_version "55" + lockfile lockfile.gsub(system_bundler_version, "2.12.0.a") + sys_exec "#{bundled_app("bin/bundle")} install" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (2.12.0.a) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '2.12.0.a'`") + end + end + + context "when update --bundler is called" do + before { lockfile.gsub(system_bundler_version, "1.1.1") } + + it "calls through to the latest bundler version" do + sys_exec! "#{bundled_app("bin/bundle")} update --bundler" + expect(last_command.stdout).to eq %(system bundler #{system_bundler_version}\n["update", "--bundler"]) + end + + it "calls through to the explicit bundler version" do + sys_exec "#{bundled_app("bin/bundle")} update --bundler=999.999.999" + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end + + context "without a lockfile" do + it "falls back to the latest installed bundler" do + FileUtils.rm bundled_app("Gemfile.lock") + sys_exec! bundled_app("bin/bundle").to_s + expect(out).to eq "system bundler #{system_bundler_version}\n[]" + end + end + + context "using another binstub" do + let(:system_bundler_version) { :bundler } + it "loads all gems" do + sys_exec! bundled_app("bin/print_loaded_gems").to_s + # RG < 2.0.14 didn't have a `Gem::Specification#default_gem?` + # This is dirty detection for old RG versions. + if File.dirname(Bundler.load.specs["bundler"][0].loaded_from) =~ %r{specifications/default} + expect(out).to eq %(["prints_loaded_gems-1.0", "rack-1.2"]) + else + expect(out).to eq %(["bundler-#{Bundler::VERSION}", "prints_loaded_gems-1.0", "rack-1.2"]) + end + end + + context "when requesting a different bundler version" do + before { lockfile lockfile.gsub(Bundler::VERSION, "999.999.999") } + + it "attempts to load that version", :ruby_repo do + sys_exec bundled_app("bin/rackup").to_s + expect(exitstatus).to eq(42) if exitstatus + expect(last_command.stderr).to include("Activating bundler (999.999.999) failed:"). + and include("To install the version of bundler this project requires, run `gem install bundler -v '999.999.999'`") + end + end + end + end + + it "installs binstubs from git gems" do + FileUtils.mkdir_p(lib_path("foo/bin")) + FileUtils.touch(lib_path("foo/bin/foo")) + build_git "foo", "1.0", :path => lib_path("foo") do |s| + s.executables = %w[foo] + end + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo")}" + G + + bundle "binstubs foo" + + expect(bundled_app("bin/foo")).to exist + end + + it "installs binstubs from path gems" do + FileUtils.mkdir_p(lib_path("foo/bin")) + FileUtils.touch(lib_path("foo/bin/foo")) + build_lib "foo", "1.0", :path => lib_path("foo") do |s| + s.executables = %w[foo] + end + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo")}" + G + + bundle "binstubs foo" + + expect(bundled_app("bin/foo")).to exist + end + + it "sets correct permissions for binstubs" do + with_umask(0o002) do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack" + binary = bundled_app("bin/rackup") + expect(File.stat(binary).mode.to_s(8)).to eq("100775") + end + end + + context "when using --shebang" do + it "sets the specified shebang for the the binstub" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack --shebang jruby" + + expect(File.open("bin/rackup").gets).to eq("#!/usr/bin/env jruby\n") + end + end + end + + context "when the gem doesn't exist" do + it "displays an error with correct status" do + install_gemfile <<-G + source "file://#{gem_repo1}" + G + + bundle "binstubs doesnt_exist" + + expect(exitstatus).to eq(7) if exitstatus + expect(out).to include("Could not find gem 'doesnt_exist'.") + end + end + + context "--path" do + it "sets the binstubs dir" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack --path exec" + + expect(bundled_app("exec/rackup")).to exist + end + + it "setting is saved for bundle install", :bundler => "< 2" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rails" + G + + bundle! "binstubs rack", forgotten_command_line_options([:path, :bin] => "exec") + bundle! :install + + expect(bundled_app("exec/rails")).to exist + end + end + + context "with --standalone option" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + it "generates a standalone binstub" do + bundle! "binstubs rack --standalone" + expect(bundled_app("bin/rackup")).to exist + end + + it "generates a binstub that does not depend on rubygems or bundler" do + bundle! "binstubs rack --standalone" + expect(File.read(bundled_app("bin/rackup"))).to_not include("Gem.bin_path") + end + + context "when specified --path option" do + it "generates a standalone binstub at the given path" do + bundle! "binstubs rack --standalone --path foo" + expect(bundled_app("foo/rackup")).to exist + end + end + end + + context "when the bin already exists" do + it "doesn't overwrite and warns" do + FileUtils.mkdir_p(bundled_app("bin")) + File.open(bundled_app("bin/rackup"), "wb") do |file| + file.print "OMG" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack" + + expect(bundled_app("bin/rackup")).to exist + expect(File.read(bundled_app("bin/rackup"))).to eq("OMG") + expect(out).to include("Skipped rackup") + expect(out).to include("overwrite skipped stubs, use --force") + end + + context "when using --force" do + it "overwrites the binstub" do + FileUtils.mkdir_p(bundled_app("bin")) + File.open(bundled_app("bin/rackup"), "wb") do |file| + file.print "OMG" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "binstubs rack --force" + + expect(bundled_app("bin/rackup")).to exist + expect(File.read(bundled_app("bin/rackup"))).not_to eq("OMG") + end + end + end + + context "when the gem has no bins" do + it "suggests child gems if they have bins" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack-obama" + G + + bundle "binstubs rack-obama" + expect(out).to include("rack-obama has no executables") + expect(out).to include("rack has: rackup") + end + + it "works if child gems don't have bins" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "actionpack" + G + + bundle "binstubs actionpack" + expect(out).to include("no executables for the gem actionpack") + end + + it "works if the gem has development dependencies" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "with_development_dependency" + G + + bundle "binstubs with_development_dependency" + expect(out).to include("no executables for the gem with_development_dependency") + end + end + + context "when BUNDLE_INSTALL is specified" do + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config auto_install 1" + bundle "binstubs rack" + expect(out).to include("Installing rack 1.0.0") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "does nothing when already up to date" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "config auto_install 1" + bundle "binstubs rack", :env => { "BUNDLE_INSTALL" => 1 } + expect(out).not_to include("Installing rack 1.0.0") + end + end +end diff --git a/spec/bundler/commands/check_spec.rb b/spec/bundler/commands/check_spec.rb new file mode 100644 index 0000000000..f2af446fbf --- /dev/null +++ b/spec/bundler/commands/check_spec.rb @@ -0,0 +1,354 @@ +# frozen_string_literal: true + +RSpec.describe "bundle check" do + it "returns success when the Gemfile is satisfied" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle :check + expect(exitstatus).to eq(0) if exitstatus + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "works with the --gemfile flag when not in the directory" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + Dir.chdir tmp + bundle "check --gemfile bundled_app/Gemfile" + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "creates a Gemfile.lock by default if one does not exist" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + FileUtils.rm("Gemfile.lock") + + bundle "check" + + expect(bundled_app("Gemfile.lock")).to exist + end + + it "does not create a Gemfile.lock if --dry-run was passed" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + FileUtils.rm("Gemfile.lock") + + bundle "check --dry-run" + + expect(bundled_app("Gemfile.lock")).not_to exist + end + + it "prints a generic error if the missing gems are unresolvable" do + system_gems ["rails-2.3.2"] + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle :check + expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.") + end + + it "prints a generic error if a Gemfile.lock does not exist and a toplevel dependency does not exist" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle :check + expect(exitstatus).to be > 0 if exitstatus + expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.") + end + + it "prints a generic message if you changed your lockfile" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rails' + G + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rails_fail' + G + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "rails_fail" + G + + bundle :check + expect(out).to include("Bundler can't satisfy your Gemfile's dependencies.") + end + + it "remembers --without option from install", :bundler => "< 2" do + gemfile <<-G + source "file://#{gem_repo1}" + group :foo do + gem "rack" + end + G + + bundle! "install --without foo" + bundle! "check" + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "uses the without setting" do + bundle! "config without foo" + install_gemfile! <<-G + source "file://#{gem_repo1}" + group :foo do + gem "rack" + end + G + + bundle! "check" + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "ensures that gems are actually installed and not just cached" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", :group => :foo + G + + bundle :install, forgotten_command_line_options(:without => "foo") + + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle "check" + expect(out).to include("* rack (1.0.0)") + expect(exitstatus).to eq(1) if exitstatus + end + + it "ignores missing gems restricted to other platforms" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + platforms :#{not_local_tag} do + gem "activesupport" + end + G + + system_gems "rack-1.0.0", :path => :bundle_path + + lockfile <<-G + GEM + remote: file:#{gem_repo1}/ + specs: + activesupport (2.3.5) + rack (1.0.0) + + PLATFORMS + #{local} + #{not_local} + + DEPENDENCIES + rack + activesupport + G + + bundle :check + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "works with env conditionals" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + env :NOT_GOING_TO_BE_SET do + gem "activesupport" + end + G + + system_gems "rack-1.0.0", :path => :bundle_path + + lockfile <<-G + GEM + remote: file:#{gem_repo1}/ + specs: + activesupport (2.3.5) + rack (1.0.0) + + PLATFORMS + #{local} + #{not_local} + + DEPENDENCIES + rack + activesupport + G + + bundle :check + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "outputs an error when the default Gemfile is not found" do + bundle :check + expect(exitstatus).to eq(10) if exitstatus + expect(out).to include("Could not locate Gemfile") + end + + it "does not output fatal error message" do + bundle :check + expect(exitstatus).to eq(10) if exitstatus + expect(out).not_to include("Unfortunately, a fatal error has occurred. ") + end + + it "should not crash when called multiple times on a new machine" do + gemfile <<-G + gem 'rails', '3.0.0.beta3' + gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git' + G + + simulate_new_machine + bundle "check" + last_out = out + 3.times do + bundle :check + expect(out).to eq(last_out) + end + end + + it "fails when there's no lock file and frozen is set" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "foo" + G + + bundle! "install", forgotten_command_line_options(:deployment => true) + FileUtils.rm(bundled_app("Gemfile.lock")) + + bundle :check + expect(last_command).to be_failure + end + + context "--path", :bundler => "< 2" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + bundle "install --path vendor/bundle" + + FileUtils.rm_rf(bundled_app(".bundle")) + end + + it "returns success" do + bundle! "check --path vendor/bundle" + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "should write to .bundle/config", :bundler => "< 2" do + bundle "check --path vendor/bundle" + bundle! "check" + end + end + + context "--path vendor/bundle after installing gems in the default directory" do + it "returns false" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle "check --path vendor/bundle" + expect(exitstatus).to eq(1) if exitstatus + expect(out).to match(/The following gems are missing/) + end + end + + describe "when locked" do + before :each do + system_gems "rack-1.0.0" + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0" + G + end + + it "returns success when the Gemfile is satisfied" do + bundle :install + bundle :check + expect(exitstatus).to eq(0) if exitstatus + expect(out).to include("The Gemfile's dependencies are satisfied") + end + + it "shows what is missing with the current Gemfile if it is not satisfied" do + simulate_new_machine + bundle :check + expect(out).to match(/The following gems are missing/) + expect(out).to include("* rack (1.0") + end + end + + describe "BUNDLED WITH" do + def lock_with(bundler_version = nil) + lock = <<-L + GEM + remote: file:#{gem_repo1}/ + specs: + rack (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + rack + L + + if bundler_version + lock += "\n BUNDLED WITH\n #{bundler_version}\n" + end + + lock + end + + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "is not present" do + it "does not change the lock" do + lockfile lock_with(nil) + bundle :check + lockfile_should_be lock_with(nil) + end + end + + context "is newer" do + it "does not change the lock but warns" do + lockfile lock_with(Bundler::VERSION.succ) + bundle! :check + expect(last_command.bundler_err).to include("the running version of Bundler (#{Bundler::VERSION}) is older than the version that created the lockfile (#{Bundler::VERSION.succ})") + lockfile_should_be lock_with(Bundler::VERSION.succ) + end + end + + context "is older" do + it "does not change the lock" do + lockfile lock_with("1.10.1") + bundle :check + lockfile_should_be lock_with("1.10.1") + end + end + end +end diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb new file mode 100644 index 0000000000..37cbeeb4e7 --- /dev/null +++ b/spec/bundler/commands/clean_spec.rb @@ -0,0 +1,771 @@ +# frozen_string_literal: true + +RSpec.describe "bundle clean" do + def should_have_gems(*gems) + gems.each do |g| + expect(vendored_gems("gems/#{g}")).to exist + expect(vendored_gems("specifications/#{g}.gemspec")).to exist + expect(vendored_gems("cache/#{g}.gem")).to exist + end + end + + def should_not_have_gems(*gems) + gems.each do |g| + expect(vendored_gems("gems/#{g}")).not_to exist + expect(vendored_gems("specifications/#{g}.gemspec")).not_to exist + expect(vendored_gems("cache/#{g}.gem")).not_to exist + end + end + + it "removes unused gems that are different" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + G + bundle! "install" + + bundle! :clean + + expect(out).to include("Removing foo (1.0)") + + should_have_gems "thin-1.0", "rack-1.0.0" + should_not_have_gems "foo-1.0" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "removes old version of gem if unused" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "0.9.1" + gem "foo" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + gem "foo" + G + bundle "install" + + bundle :clean + + expect(out).to include("Removing rack (0.9.1)") + + should_have_gems "foo-1.0", "rack-1.0.0" + should_not_have_gems "rack-0.9.1" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "removes new version of gem if unused" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + gem "foo" + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "0.9.1" + gem "foo" + G + bundle! "update rack" + + bundle! :clean + + expect(out).to include("Removing rack (1.0.0)") + + should_have_gems "foo-1.0", "rack-0.9.1" + should_not_have_gems "rack-1.0.0" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "removes gems in bundle without groups" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + + group :test_group do + gem "rack", "1.0.0" + end + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + bundle "install", forgotten_command_line_options(:without => "test_group") + bundle :clean + + expect(out).to include("Removing rack (1.0.0)") + + should_have_gems "foo-1.0" + should_not_have_gems "rack-1.0.0" + + expect(vendored_gems("bin/rackup")).to_not exist + end + + it "does not remove cached git dir if it's being used" do + build_git "foo" + revision = revision_for(lib_path("foo-1.0")) + git_path = lib_path("foo-1.0") + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + git "#{git_path}", :ref => "#{revision}" do + gem "foo" + end + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + bundle :clean + + digest = Digest(:SHA1).hexdigest(git_path.to_s) + cache_path = Bundler::VERSION.start_with?("1.") ? vendored_gems("cache/bundler/git/foo-1.0-#{digest}") : home(".bundle/cache/git/foo-1.0-#{digest}") + expect(cache_path).to exist + end + + it "removes unused git gems" do + build_git "foo", :path => lib_path("foo") + git_path = lib_path("foo") + revision = revision_for(git_path) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + git "#{git_path}", :ref => "#{revision}" do + gem "foo" + end + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + G + bundle "install" + + bundle :clean + + expect(out).to include("Removing foo (#{revision[0..11]})") + + expect(vendored_gems("gems/rack-1.0.0")).to exist + expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).not_to exist + digest = Digest(:SHA1).hexdigest(git_path.to_s) + expect(vendored_gems("cache/bundler/git/foo-#{digest}")).not_to exist + + expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist + + expect(vendored_gems("bin/rackup")).to exist + end + + it "removes old git gems" do + build_git "foo-bar", :path => lib_path("foo-bar") + revision = revision_for(lib_path("foo-bar")) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + git "#{lib_path("foo-bar")}" do + gem "foo-bar" + end + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle") + + update_git "foo", :path => lib_path("foo-bar") + revision2 = revision_for(lib_path("foo-bar")) + + bundle! "update", :all => bundle_update_requires_all? + bundle! :clean + + expect(out).to include("Removing foo-bar (#{revision[0..11]})") + + expect(vendored_gems("gems/rack-1.0.0")).to exist + expect(vendored_gems("bundler/gems/foo-bar-#{revision[0..11]}")).not_to exist + expect(vendored_gems("bundler/gems/foo-bar-#{revision2[0..11]}")).to exist + + expect(vendored_gems("specifications/rack-1.0.0.gemspec")).to exist + + expect(vendored_gems("bin/rackup")).to exist + end + + it "does not remove nested gems in a git repo" do + build_lib "activesupport", "3.0", :path => lib_path("rails/activesupport") + build_git "rails", "3.0", :path => lib_path("rails") do |s| + s.add_dependency "activesupport", "= 3.0" + end + revision = revision_for(lib_path("rails")) + + gemfile <<-G + gem "activesupport", :git => "#{lib_path("rails")}", :ref => '#{revision}' + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + bundle :clean + expect(out).to include("") + + expect(vendored_gems("bundler/gems/rails-#{revision[0..11]}")).to exist + end + + it "does not remove git sources that are in without groups" do + build_git "foo", :path => lib_path("foo") + git_path = lib_path("foo") + revision = revision_for(git_path) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + group :test do + git "#{git_path}", :ref => "#{revision}" do + gem "foo" + end + end + G + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :without => "test") + + bundle :clean + + expect(out).to include("") + expect(vendored_gems("bundler/gems/foo-#{revision[0..11]}")).to exist + digest = Digest(:SHA1).hexdigest(git_path.to_s) + expect(vendored_gems("cache/bundler/git/foo-#{digest}")).to_not exist + end + + it "does not blow up when using without groups" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + + group :development do + gem "foo" + end + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :without => "development") + + bundle :clean + expect(exitstatus).to eq(0) if exitstatus + end + + it "displays an error when used without --path" do + bundle! "config path.system true" + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", "1.0.0" + G + + bundle :clean + + expect(exitstatus).to eq(15) if exitstatus + expect(out).to include("--force") + end + + # handling bundle clean upgrade path from the pre's + it "removes .gem/.gemspec file even if there's no corresponding gem dir" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + G + bundle "install" + + FileUtils.rm(vendored_gems("bin/rackup")) + FileUtils.rm_rf(vendored_gems("gems/thin-1.0")) + FileUtils.rm_rf(vendored_gems("gems/rack-1.0.0")) + + bundle :clean + + should_not_have_gems "thin-1.0", "rack-1.0" + should_have_gems "foo-1.0" + + expect(vendored_gems("bin/rackup")).not_to exist + end + + it "does not call clean automatically when using system gems" do + bundle! "config path.system true" + + bundle! :config + + install_gemfile! <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "rack" + G + + bundle! "info thin" + + install_gemfile! <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + + sys_exec! "gem list" + expect(out).to include("rack (1.0.0)").and include("thin (1.0)") + end + + it "--clean should override the bundle setting on install", :bundler => "< 2" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "rack" + G + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => true) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + bundle "install" + + should_have_gems "rack-1.0.0" + should_not_have_gems "thin-1.0" + end + + it "--clean should override the bundle setting on update", :bundler => "< 2" do + build_repo2 + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "foo" + G + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => true) + + update_repo2 do + build_gem "foo", "1.0.1" + end + + bundle! "update", :all => bundle_update_requires_all? + + should_have_gems "foo-1.0.1" + should_not_have_gems "foo-1.0" + end + + it "automatically cleans when path has not been set", :bundler => "2" do + build_repo2 + + install_gemfile! <<-G + source "file://#{gem_repo2}" + + gem "foo" + G + + update_repo2 do + build_gem "foo", "1.0.1" + end + + bundle! "update", :all => true + + files = Pathname.glob(bundled_app(".bundle", Bundler.ruby_scope, "*", "*")) + files.map! {|f| f.to_s.sub(bundled_app(".bundle", Bundler.ruby_scope).to_s, "") } + expect(files.sort).to eq %w[ + /cache/foo-1.0.1.gem + /gems/foo-1.0.1 + /specifications/foo-1.0.1.gemspec + ] + end + + it "does not clean automatically on --path" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "rack" + G + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + bundle "install" + + should_have_gems "rack-1.0.0", "thin-1.0" + end + + it "does not clean on bundle update with --path" do + build_repo2 + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "foo" + G + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle") + + update_repo2 do + build_gem "foo", "1.0.1" + end + + bundle! :update, :all => bundle_update_requires_all? + should_have_gems "foo-1.0", "foo-1.0.1" + end + + it "does not clean on bundle update when using --system" do + bundle! "config path.system true" + + build_repo2 + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "foo" + G + bundle! "install" + + update_repo2 do + build_gem "foo", "1.0.1" + end + bundle! :update, :all => bundle_update_requires_all? + + sys_exec! "gem list" + expect(out).to include("foo (1.0.1, 1.0)") + end + + it "cleans system gems when --force is used" do + bundle! "config path.system true" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + gem "rack" + G + bundle :install + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + bundle :install + bundle "clean --force" + + expect(out).to include("Removing foo (1.0)") + sys_exec "gem list" + expect(out).not_to include("foo (1.0)") + expect(out).to include("rack (1.0.0)") + end + + describe "when missing permissions" do + before { ENV["BUNDLE_PATH__SYSTEM"] = "true" } + let(:system_cache_path) { system_gem_path("cache") } + after do + FileUtils.chmod(0o755, system_cache_path) + end + it "returns a helpful error message" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + gem "rack" + G + bundle :install + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + bundle :install + + FileUtils.chmod(0o500, system_cache_path) + + bundle :clean, :force => true + + expect(out).to include(system_gem_path.to_s) + expect(out).to include("grant write permissions") + + sys_exec "gem list" + expect(out).to include("foo (1.0)") + expect(out).to include("rack (1.0.0)") + end + end + + it "cleans git gems with a 7 length git revision" do + build_git "foo" + revision = revision_for(lib_path("foo-1.0")) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + # mimic 7 length git revisions in Gemfile.lock + gemfile_lock = File.read(bundled_app("Gemfile.lock")).split("\n") + gemfile_lock.each_with_index do |line, index| + gemfile_lock[index] = line[0..(11 + 7)] if line.include?(" revision:") + end + File.open(bundled_app("Gemfile.lock"), "w") do |file| + file.print gemfile_lock.join("\n") + end + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + bundle :clean + + expect(out).not_to include("Removing foo (1.0 #{revision[0..6]})") + + expect(vendored_gems("bundler/gems/foo-1.0-#{revision[0..6]}")).to exist + end + + it "when using --force on system gems, it doesn't remove binaries" do + bundle! "config path.system true" + + build_repo2 + update_repo2 do + build_gem "bindir" do |s| + s.bindir = "exe" + s.executables = "foo" + end + end + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "bindir" + G + bundle :install + + bundle "clean --force" + + sys_exec "foo" + + expect(exitstatus).to eq(0) if exitstatus + expect(out).to eq("1.0") + end + + it "doesn't blow up on path gems without a .gempsec" do + relative_path = "vendor/private_gems/bar-1.0" + absolute_path = bundled_app(relative_path) + FileUtils.mkdir_p("#{absolute_path}/lib/bar") + File.open("#{absolute_path}/lib/bar/bar.rb", "wb") do |file| + file.puts "module Bar; end" + end + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "foo" + gem "bar", "1.0", :path => "#{relative_path}" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + bundle! :clean + end + + it "doesn't remove gems in dry-run mode with path set" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + G + + bundle :install + + bundle "clean --dry-run" + + expect(out).not_to include("Removing foo (1.0)") + expect(out).to include("Would have removed foo (1.0)") + + should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "doesn't remove gems in dry-run mode with no path set" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + G + + bundle :install + + bundle "configuration --delete path" + + bundle "clean --dry-run" + + expect(out).not_to include("Removing foo (1.0)") + expect(out).to include("Would have removed foo (1.0)") + + should_have_gems "thin-1.0", "rack-1.0.0", "foo-1.0" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "doesn't store dry run as a config setting" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + bundle "config dry_run false" + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + G + + bundle :install + + bundle "clean" + + expect(out).to include("Removing foo (1.0)") + expect(out).not_to include("Would have removed foo (1.0)") + + should_have_gems "thin-1.0", "rack-1.0.0" + should_not_have_gems "foo-1.0" + + expect(vendored_gems("bin/rackup")).to exist + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "foo" + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle", :clean => false) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "weakling" + G + + bundle! "config auto_install 1" + bundle! :clean + expect(out).to include("Installing weakling 0.0.3") + should_have_gems "thin-1.0", "rack-1.0.0", "weakling-0.0.3" + should_not_have_gems "foo-1.0" + end + + it "doesn't remove extensions artifacts from bundled git gems after clean", :ruby_repo, :rubygems => "2.2" do + build_git "very_simple_git_binary", &:add_c_extension + + revision = revision_for(lib_path("very_simple_git_binary-1.0")) + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "very_simple_git_binary", :git => "#{lib_path("very_simple_git_binary-1.0")}", :ref => "#{revision}" + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle") + expect(vendored_gems("bundler/gems/extensions")).to exist + expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist + + bundle! :clean + expect(out).to eq("") + + expect(vendored_gems("bundler/gems/extensions")).to exist + expect(vendored_gems("bundler/gems/very_simple_git_binary-1.0-#{revision[0..11]}")).to exist + end + + it "removes extension directories", :ruby_repo, :rubygems => "2.2" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "very_simple_binary" + gem "simple_binary" + G + + bundle! "install", forgotten_command_line_options(:path => "vendor/bundle") + + very_simple_binary_extensions_dir = + Pathname.glob("#{vendored_gems}/extensions/*/*/very_simple_binary-1.0").first + + simple_binary_extensions_dir = + Pathname.glob("#{vendored_gems}/extensions/*/*/simple_binary-1.0").first + + expect(very_simple_binary_extensions_dir).to exist + expect(simple_binary_extensions_dir).to exist + + gemfile <<-G + source "file://#{gem_repo1}" + + gem "thin" + gem "simple_binary" + G + + bundle! "install" + bundle! :clean + expect(out).to eq("Removing very_simple_binary (1.0)") + + expect(very_simple_binary_extensions_dir).not_to exist + expect(simple_binary_extensions_dir).to exist + end +end diff --git a/spec/bundler/commands/config_spec.rb b/spec/bundler/commands/config_spec.rb new file mode 100644 index 0000000000..9e49357465 --- /dev/null +++ b/spec/bundler/commands/config_spec.rb @@ -0,0 +1,384 @@ +# frozen_string_literal: true + +RSpec.describe ".bundle/config" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0.0" + G + end + + describe "config" do + before { bundle "config foo bar" } + + it "prints a detailed report of local and user configuration" do + bundle "config" + + expect(out).to include("Settings are listed in order of priority. The top value will be used") + expect(out).to include("foo\nSet for the current user") + expect(out).to include(": \"bar\"") + end + + context "given --parseable flag" do + it "prints a minimal report of local and user configuration" do + bundle "config --parseable" + expect(out).to include("foo=bar") + end + + context "with global config" do + it "prints config assigned to local scope" do + bundle "config --local foo bar2" + bundle "config --parseable" + expect(out).to include("foo=bar2") + end + end + + context "with env overwrite" do + it "prints config with env" do + bundle "config --parseable", :env => { "BUNDLE_FOO" => "bar3" } + expect(out).to include("foo=bar3") + end + end + end + end + + describe "BUNDLE_APP_CONFIG" do + it "can be moved with an environment variable" do + ENV["BUNDLE_APP_CONFIG"] = tmp("foo/bar").to_s + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + expect(bundled_app(".bundle")).not_to exist + expect(tmp("foo/bar/config")).to exist + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "can provide a relative path with the environment variable" do + FileUtils.mkdir_p bundled_app("omg") + Dir.chdir bundled_app("omg") + + ENV["BUNDLE_APP_CONFIG"] = "../foo" + bundle "install", forgotten_command_line_options(:path => "vendor/bundle") + + expect(bundled_app(".bundle")).not_to exist + expect(bundled_app("../foo/config")).to exist + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + describe "global" do + before(:each) { bundle :install } + + it "is the default" do + bundle "config foo global" + run "puts Bundler.settings[:foo]" + expect(out).to eq("global") + end + + it "can also be set explicitly" do + bundle! "config --global foo global" + run! "puts Bundler.settings[:foo]" + expect(out).to eq("global") + end + + it "has lower precedence than local" do + bundle "config --local foo local" + + bundle "config --global foo global" + expect(out).to match(/Your application has set foo to "local"/) + + run "puts Bundler.settings[:foo]" + expect(out).to eq("local") + end + + it "has lower precedence than env" do + begin + ENV["BUNDLE_FOO"] = "env" + + bundle "config --global foo global" + expect(out).to match(/You have a bundler environment variable for foo set to "env"/) + + run "puts Bundler.settings[:foo]" + expect(out).to eq("env") + ensure + ENV.delete("BUNDLE_FOO") + end + end + + it "can be deleted" do + bundle "config --global foo global" + bundle "config --delete foo" + + run "puts Bundler.settings[:foo] == nil" + expect(out).to eq("true") + end + + it "warns when overriding" do + bundle "config --global foo previous" + bundle "config --global foo global" + expect(out).to match(/You are replacing the current global value of foo/) + + run "puts Bundler.settings[:foo]" + expect(out).to eq("global") + end + + it "does not warn when using the same value twice" do + bundle "config --global foo value" + bundle "config --global foo value" + expect(out).not_to match(/You are replacing the current global value of foo/) + + run "puts Bundler.settings[:foo]" + expect(out).to eq("value") + end + + it "expands the path at time of setting" do + bundle "config --global local.foo .." + run "puts Bundler.settings['local.foo']" + expect(out).to eq(File.expand_path(Dir.pwd + "/..")) + end + + it "saves with parseable option" do + bundle "config --global --parseable foo value" + expect(out).to eq("foo=value") + run "puts Bundler.settings['foo']" + expect(out).to eq("value") + end + + context "when replacing a current value with the parseable flag" do + before { bundle "config --global foo value" } + it "prints the current value in a parseable format" do + bundle "config --global --parseable foo value2" + expect(out).to eq "foo=value2" + run "puts Bundler.settings['foo']" + expect(out).to eq("value2") + end + end + end + + describe "local" do + before(:each) { bundle :install } + + it "can also be set explicitly" do + bundle "config --local foo local" + run "puts Bundler.settings[:foo]" + expect(out).to eq("local") + end + + it "has higher precedence than env" do + begin + ENV["BUNDLE_FOO"] = "env" + bundle "config --local foo local" + + run "puts Bundler.settings[:foo]" + expect(out).to eq("local") + ensure + ENV.delete("BUNDLE_FOO") + end + end + + it "can be deleted" do + bundle "config --local foo local" + bundle "config --delete foo" + + run "puts Bundler.settings[:foo] == nil" + expect(out).to eq("true") + end + + it "warns when overriding" do + bundle "config --local foo previous" + bundle "config --local foo local" + expect(out).to match(/You are replacing the current local value of foo/) + + run "puts Bundler.settings[:foo]" + expect(out).to eq("local") + end + + it "expands the path at time of setting" do + bundle "config --local local.foo .." + run "puts Bundler.settings['local.foo']" + expect(out).to eq(File.expand_path(Dir.pwd + "/..")) + end + + it "can be deleted with parseable option" do + bundle "config --local foo value" + bundle "config --delete --parseable foo" + expect(out).to eq "" + run "puts Bundler.settings['foo'] == nil" + expect(out).to eq("true") + end + end + + describe "env" do + before(:each) { bundle :install } + + it "can set boolean properties via the environment" do + ENV["BUNDLE_FROZEN"] = "true" + + run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" + expect(out).to eq("true") + end + + it "can set negative boolean properties via the environment" do + run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" + expect(out).to eq("false") + + ENV["BUNDLE_FROZEN"] = "false" + + run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" + expect(out).to eq("false") + + ENV["BUNDLE_FROZEN"] = "0" + + run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" + expect(out).to eq("false") + + ENV["BUNDLE_FROZEN"] = "" + + run "if Bundler.settings[:frozen]; puts 'true' else puts 'false' end" + expect(out).to eq("false") + end + + it "can set properties with periods via the environment" do + ENV["BUNDLE_FOO__BAR"] = "baz" + + run "puts Bundler.settings['foo.bar']" + expect(out).to eq("baz") + end + end + + describe "parseable option" do + it "prints an empty string" do + bundle "config foo --parseable" + + expect(out).to eq "" + end + + it "only prints the value of the config" do + bundle "config foo local" + bundle "config foo --parseable" + + expect(out).to eq "foo=local" + end + + it "can print global config" do + bundle "config --global bar value" + bundle "config bar --parseable" + + expect(out).to eq "bar=value" + end + + it "prefers local config over global" do + bundle "config --local bar value2" + bundle "config --global bar value" + bundle "config bar --parseable" + + expect(out).to eq "bar=value2" + end + end + + describe "gem mirrors" do + before(:each) { bundle :install } + + it "configures mirrors using keys with `mirror.`" do + bundle "config --local mirror.http://gems.example.org http://gem-mirror.example.org" + run(<<-E) +Bundler.settings.gem_mirrors.each do |k, v| + puts "\#{k} => \#{v}" +end +E + expect(out).to eq("http://gems.example.org/ => http://gem-mirror.example.org/") + end + end + + describe "quoting" do + before(:each) { gemfile "# no gems" } + let(:long_string) do + "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ + "--with-xslt-dir=/usr/pkg" + end + + it "saves quotes" do + bundle "config foo something\\'" + run "puts Bundler.settings[:foo]" + expect(out).to eq("something'") + end + + it "doesn't return quotes around values", :ruby => "1.9" do + bundle "config foo '1'" + run "puts Bundler.settings.send(:global_config_file).read" + expect(out).to include('"1"') + run "puts Bundler.settings[:foo]" + expect(out).to eq("1") + end + + it "doesn't duplicate quotes around values", :if => (RUBY_VERSION >= "2.1") do + bundled_app(".bundle").mkpath + File.open(bundled_app(".bundle/config"), "w") do |f| + f.write 'BUNDLE_FOO: "$BUILD_DIR"' + end + + bundle "config bar baz" + run "puts Bundler.settings.send(:local_config_file).read" + + # Starting in Ruby 2.1, YAML automatically adds double quotes + # around some values, including $ and newlines. + expect(out).to include('BUNDLE_FOO: "$BUILD_DIR"') + end + + it "doesn't duplicate quotes around long wrapped values" do + bundle "config foo #{long_string}" + + run "puts Bundler.settings[:foo]" + expect(out).to eq(long_string) + + bundle "config bar baz" + + run "puts Bundler.settings[:foo]" + expect(out).to eq(long_string) + end + end + + describe "very long lines" do + before(:each) { bundle :install } + + let(:long_string) do + "--with-xml2-include=/usr/pkg/include/libxml2 --with-xml2-lib=/usr/pkg/lib " \ + "--with-xslt-dir=/usr/pkg" + end + + let(:long_string_without_special_characters) do + "here is quite a long string that will wrap to a second line but will not be " \ + "surrounded by quotes" + end + + it "doesn't wrap values" do + bundle "config foo #{long_string}" + run "puts Bundler.settings[:foo]" + expect(out).to match(long_string) + end + + it "can read wrapped unquoted values" do + bundle "config foo #{long_string_without_special_characters}" + run "puts Bundler.settings[:foo]" + expect(out).to match(long_string_without_special_characters) + end + end +end + +RSpec.describe "setting gemfile via config" do + context "when only the non-default Gemfile exists" do + it "persists the gemfile location to .bundle/config" do + File.open(bundled_app("NotGemfile"), "w") do |f| + f.write <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + end + + bundle "config --local gemfile #{bundled_app("NotGemfile")}" + expect(File.exist?(".bundle/config")).to eq(true) + + bundle "config" + expect(out).to include("NotGemfile") + end + end +end diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb new file mode 100644 index 0000000000..9bf66e8f5b --- /dev/null +++ b/spec/bundler/commands/console_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +RSpec.describe "bundle console", :bundler => "< 2" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + G + end + + it "starts IRB with the default group loaded" do + bundle "console" do |input, _, _| + input.puts("puts RACK") + input.puts("exit") + end + expect(out).to include("0.9.1") + end + + it "uses IRB as default console" do + bundle "console" do |input, _, _| + input.puts("__method__") + input.puts("exit") + end + expect(out).to include(":irb_binding") + end + + it "starts another REPL if configured as such" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "pry" + G + bundle "config console pry" + + bundle "console" do |input, _, _| + input.puts("__method__") + input.puts("exit") + end + expect(out).to include(":__pry__") + end + + it "falls back to IRB if the other REPL isn't available" do + bundle "config console pry" + # make sure pry isn't there + + bundle "console" do |input, _, _| + input.puts("__method__") + input.puts("exit") + end + expect(out).to include(":irb_binding") + end + + it "doesn't load any other groups" do + bundle "console" do |input, _, _| + input.puts("puts ACTIVESUPPORT") + input.puts("exit") + end + expect(out).to include("NameError") + end + + describe "when given a group" do + it "loads the given group" do + bundle "console test" do |input, _, _| + input.puts("puts ACTIVESUPPORT") + input.puts("exit") + end + expect(out).to include("2.3.5") + end + + it "loads the default group" do + bundle "console test" do |input, _, _| + input.puts("puts RACK") + input.puts("exit") + end + expect(out).to include("0.9.1") + end + + it "doesn't load other groups" do + bundle "console test" do |input, _, _| + input.puts("puts RACK_MIDDLEWARE") + input.puts("exit") + end + expect(out).to include("NameError") + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "activesupport", :group => :test + gem "rack_middleware", :group => :development + gem "foo" + G + + bundle "config auto_install 1" + bundle :console do |input, _, _| + input.puts("puts 'hello'") + input.puts("exit") + end + expect(out).to include("Installing foo 1.0") + expect(out).to include("hello") + expect(the_bundle).to include_gems "foo 1.0" + end +end diff --git a/spec/bundler/commands/doctor_spec.rb b/spec/bundler/commands/doctor_spec.rb new file mode 100644 index 0000000000..5260e6cb36 --- /dev/null +++ b/spec/bundler/commands/doctor_spec.rb @@ -0,0 +1,110 @@ +# frozen_string_literal: true + +require "find" +require "stringio" +require "bundler/cli" +require "bundler/cli/doctor" + +RSpec.describe "bundle doctor" do + before(:each) do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + @stdout = StringIO.new + + [:error, :warn].each do |method| + allow(Bundler.ui).to receive(method).and_wrap_original do |m, message| + m.call message + @stdout.puts message + end + end + end + + context "when all files in home are readable/writable" do + before(:each) do + stat = double("stat") + unwritable_file = double("file") + allow(Find).to receive(:find).with(Bundler.home.to_s) { [unwritable_file] } + allow(File).to receive(:stat).with(unwritable_file) { stat } + allow(stat).to receive(:uid) { Process.uid } + allow(File).to receive(:writable?).with(unwritable_file) { true } + allow(File).to receive(:readable?).with(unwritable_file) { true } + end + + it "exits with no message if the installed gem has no C extensions" do + expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect(@stdout.string).to be_empty + end + + it "exits with no message if the installed gem's C extension dylib breakage is fine" do + doctor = Bundler::CLI::Doctor.new({}) + expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] + expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/lib/libSystem.dylib"] + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with("/usr/lib/libSystem.dylib").and_return(true) + expect { doctor.run }.not_to(raise_error, @stdout.string) + expect(@stdout.string).to be_empty + end + + it "exits with a message if one of the linked libraries is missing" do + doctor = Bundler::CLI::Doctor.new({}) + expect(doctor).to receive(:bundles_for_gem).exactly(2).times.and_return ["/path/to/rack/rack.bundle"] + expect(doctor).to receive(:dylibs).exactly(2).times.and_return ["/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib"] + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:exist?).with("/usr/local/opt/icu4c/lib/libicui18n.57.1.dylib").and_return(false) + expect { doctor.run }.to raise_error(Bundler::ProductionError, strip_whitespace(<<-E).strip), @stdout.string + The following gems are missing OS dependencies: + * bundler: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib + * rack: /usr/local/opt/icu4c/lib/libicui18n.57.1.dylib + E + end + end + + context "when home contains files that are not readable/writable" do + before(:each) do + @stat = double("stat") + @unwritable_file = double("file") + allow(Find).to receive(:find).with(Bundler.home.to_s) { [@unwritable_file] } + allow(File).to receive(:stat).with(@unwritable_file) { @stat } + end + + it "exits with an error if home contains files that are not readable/writable" do + allow(@stat).to receive(:uid) { Process.uid } + allow(File).to receive(:writable?).with(@unwritable_file) { false } + allow(File).to receive(:readable?).with(@unwritable_file) { false } + expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect(@stdout.string).to include( + "Files exist in the Bundler home that are not readable/writable by the current user. These files are:\n - #{@unwritable_file}" + ) + expect(@stdout.string).not_to include("No issues") + end + + context "when home contains files that are not owned by the current process" do + before(:each) do + allow(@stat).to receive(:uid) { 0o0000 } + end + + it "exits with an error if home contains files that are not readable/writable and are not owned by the current user" do + allow(File).to receive(:writable?).with(@unwritable_file) { false } + allow(File).to receive(:readable?).with(@unwritable_file) { false } + expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect(@stdout.string).to include( + "Files exist in the Bundler home that are owned by another user, and are not readable/writable. These files are:\n - #{@unwritable_file}" + ) + expect(@stdout.string).not_to include("No issues") + end + + it "exits with a warning if home contains files that are read/write but not owned by current user" do + allow(File).to receive(:writable?).with(@unwritable_file) { true } + allow(File).to receive(:readable?).with(@unwritable_file) { true } + expect { Bundler::CLI::Doctor.new({}).run }.not_to raise_error + expect(@stdout.string).to include( + "Files exist in the Bundler home that are owned by another user, but are still readable/writable. These files are:\n - #{@unwritable_file}" + ) + expect(@stdout.string).not_to include("No issues") + end + end + end +end diff --git a/spec/bundler/commands/exec_spec.rb b/spec/bundler/commands/exec_spec.rb new file mode 100644 index 0000000000..6835305d55 --- /dev/null +++ b/spec/bundler/commands/exec_spec.rb @@ -0,0 +1,858 @@ +# frozen_string_literal: true + +RSpec.describe "bundle exec" do + let(:system_gems_to_install) { %w[rack-1.0.0 rack-0.9.1] } + before :each do + system_gems(system_gems_to_install, :path => :bundle_path) + end + + it "works with --gemfile flag" do + create_file "CustomGemfile", <<-G + gem "rack", "1.0.0" + G + + bundle "exec --gemfile CustomGemfile rackup" + expect(out).to eq("1.0.0") + end + + it "activates the correct gem" do + gemfile <<-G + gem "rack", "0.9.1" + G + + bundle "exec rackup" + expect(out).to eq("0.9.1") + end + + it "works when the bins are in ~/.bundle" do + install_gemfile <<-G + gem "rack" + G + + bundle "exec rackup" + expect(out).to eq("1.0.0") + end + + it "works when running from a random directory", :ruby_repo do + install_gemfile <<-G + gem "rack" + G + + bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + + expect(out).to include("1.0.0") + end + + it "works when exec'ing something else" do + install_gemfile 'gem "rack"' + bundle "exec echo exec" + expect(out).to eq("exec") + end + + it "works when exec'ing to ruby" do + install_gemfile 'gem "rack"' + bundle "exec ruby -e 'puts %{hi}'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + expect(out).to eq("hi") + end + + it "accepts --verbose" do + install_gemfile 'gem "rack"' + bundle "exec --verbose echo foobar" + expect(out).to eq("foobar") + end + + it "passes --verbose to command if it is given after the command" do + install_gemfile 'gem "rack"' + bundle "exec echo --verbose" + expect(out).to eq("--verbose") + end + + it "handles --keep-file-descriptors" do + require "tempfile" + + command = Tempfile.new("io-test") + command.sync = true + command.write <<-G + if ARGV[0] + IO.for_fd(ARGV[0].to_i) + else + require 'tempfile' + io = Tempfile.new("io-test-fd") + args = %W[#{Gem.ruby} -I#{lib} #{bindir.join("bundle")} exec --keep-file-descriptors #{Gem.ruby} #{command.path} \#{io.to_i}] + args << { io.to_i => io } if RUBY_VERSION >= "2.0" + exec(*args) + end + G + + install_gemfile "" + with_env_vars "RUBYOPT" => "-r#{spec_dir.join("support/hax")}" do + sys_exec "#{Gem.ruby} #{command.path}" + end + + if Bundler.current_ruby.ruby_2? + expect(out).to eq("") + else + expect(out).to eq("Ruby version #{RUBY_VERSION} defaults to keeping non-standard file descriptors on Kernel#exec.") + end + + expect(err).to lack_errors + end + + it "accepts --keep-file-descriptors" do + install_gemfile "" + bundle "exec --keep-file-descriptors echo foobar" + + expect(err).to lack_errors + end + + it "can run a command named --verbose" do + install_gemfile 'gem "rack"' + File.open("--verbose", "w") do |f| + f.puts "#!/bin/sh" + f.puts "echo foobar" + end + File.chmod(0o744, "--verbose") + with_path_as(".") do + bundle "exec -- --verbose" + end + expect(out).to eq("foobar") + end + + it "handles different versions in different bundles" do + build_repo2 do + build_gem "rack_two", "1.0.0" do |s| + s.executables = "rackup" + end + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + G + + Dir.chdir bundled_app2 do + install_gemfile bundled_app2("Gemfile"), <<-G + source "file://#{gem_repo2}" + gem "rack_two", "1.0.0" + G + end + + bundle! "exec rackup" + + expect(out).to eq("0.9.1") + + Dir.chdir bundled_app2 do + bundle! "exec rackup" + expect(out).to eq("1.0.0") + end + end + + it "handles gems installed with --without" do + install_gemfile <<-G, forgotten_command_line_options(:without => "middleware") + source "file://#{gem_repo1}" + gem "rack" # rack 0.9.1 and 1.0 exist + + group :middleware do + gem "rack_middleware" # rack_middleware depends on rack 0.9.1 + end + G + + bundle "exec rackup" + + expect(out).to eq("0.9.1") + expect(the_bundle).not_to include_gems "rack_middleware 1.0" + end + + it "does not duplicate already exec'ed RUBYOPT" do + install_gemfile <<-G + gem "rack" + G + + rubyopt = ENV["RUBYOPT"] + rubyopt = "-rbundler/setup #{rubyopt}" + + bundle "exec 'echo $RUBYOPT'" + expect(out).to have_rubyopts(rubyopt) + + bundle "exec 'echo $RUBYOPT'", :env => { "RUBYOPT" => rubyopt } + expect(out).to have_rubyopts(rubyopt) + end + + it "does not duplicate already exec'ed RUBYLIB" do + install_gemfile <<-G + gem "rack" + G + + rubylib = ENV["RUBYLIB"] + rubylib = "#{rubylib}".split(File::PATH_SEPARATOR).unshift "#{bundler_path}" + rubylib = rubylib.uniq.join(File::PATH_SEPARATOR) + + bundle "exec 'echo $RUBYLIB'" + expect(out).to include(rubylib) + + bundle "exec 'echo $RUBYLIB'", :env => { "RUBYLIB" => rubylib } + expect(out).to include(rubylib) + end + + it "errors nicely when the argument doesn't exist" do + install_gemfile <<-G + gem "rack" + G + + bundle "exec foobarbaz" + expect(exitstatus).to eq(127) if exitstatus + expect(out).to include("bundler: command not found: foobarbaz") + expect(out).to include("Install missing gem executables with `bundle install`") + end + + it "errors nicely when the argument is not executable" do + install_gemfile <<-G + gem "rack" + G + + bundle "exec touch foo" + bundle "exec ./foo" + expect(exitstatus).to eq(126) if exitstatus + expect(out).to include("bundler: not executable: ./foo") + end + + it "errors nicely when no arguments are passed" do + install_gemfile <<-G + gem "rack" + G + + bundle "exec" + expect(exitstatus).to eq(128) if exitstatus + expect(out).to include("bundler: exec needs a command to run") + end + + it "raises a helpful error when exec'ing to something outside of the bundle", :ruby_repo, :rubygems => ">= 2.5.2" do + bundle! "config clean false" # want to keep the rackup binstub + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "with_license" + G + [true, false].each do |l| + bundle! "config disable_exec_load #{l}" + bundle "exec rackup" + expect(last_command.stderr).to include "can't find executable rackup for gem rack. rack is not currently included in the bundle, perhaps you meant to add it to your Gemfile?" + end + end + + # Different error message on old RG versions (before activate_bin_path) because they + # called `Kernel#gem` directly + it "raises a helpful error when exec'ing to something outside of the bundle", :rubygems => "< 2.5.2" do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "with_license" + G + [true, false].each do |l| + bundle! "config disable_exec_load #{l}" + bundle "exec rackup", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + expect(last_command.stderr).to include "rack is not part of the bundle. Add it to your Gemfile." + end + end + + describe "with help flags" do + each_prefix = proc do |string, &blk| + 1.upto(string.length) {|l| blk.call(string[0, l]) } + end + each_prefix.call("exec") do |exec| + describe "when #{exec} is used" do + before(:each) do + install_gemfile <<-G + gem "rack" + G + + create_file("print_args", <<-'RUBY') + #!/usr/bin/env ruby + puts "args: #{ARGV.inspect}" + RUBY + bundled_app("print_args").chmod(0o755) + end + + it "shows executable's man page when --help is after the executable" do + bundle "#{exec} print_args --help" + expect(out).to eq('args: ["--help"]') + end + + it "shows executable's man page when --help is after the executable and an argument" do + bundle "#{exec} print_args foo --help" + expect(out).to eq('args: ["foo", "--help"]') + + bundle "#{exec} print_args foo bar --help" + expect(out).to eq('args: ["foo", "bar", "--help"]') + + bundle "#{exec} print_args foo --help bar" + expect(out).to eq('args: ["foo", "--help", "bar"]') + end + + it "shows executable's man page when the executable has a -" do + FileUtils.mv(bundled_app("print_args"), bundled_app("docker-template")) + bundle "#{exec} docker-template build discourse --help" + expect(out).to eq('args: ["build", "discourse", "--help"]') + end + + it "shows executable's man page when --help is after another flag" do + bundle "#{exec} print_args --bar --help" + expect(out).to eq('args: ["--bar", "--help"]') + end + + it "uses executable's original behavior for -h" do + bundle "#{exec} print_args -h" + expect(out).to eq('args: ["-h"]') + end + + it "shows bundle-exec's man page when --help is between exec and the executable" do + with_fake_man do + bundle "#{exec} --help cat" + end + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) + end + + it "shows bundle-exec's man page when --help is before exec" do + with_fake_man do + bundle "--help #{exec}" + end + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) + end + + it "shows bundle-exec's man page when -h is before exec" do + with_fake_man do + bundle "-h #{exec}" + end + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) + end + + it "shows bundle-exec's man page when --help is after exec" do + with_fake_man do + bundle "#{exec} --help" + end + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) + end + + it "shows bundle-exec's man page when -h is after exec" do + with_fake_man do + bundle "#{exec} -h" + end + expect(out).to include(%(["#{root}/man/bundle-exec.1"])) + end + end + end + end + + describe "with gem executables" do + describe "run from a random directory", :ruby_repo do + before(:each) do + install_gemfile <<-G + gem "rack" + G + end + + it "works when unlocked" do + bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + expect(out).to eq("1.0.0") + expect(out).to include("1.0.0") + end + + it "works when locked" do + expect(the_bundle).to be_locked + bundle "exec 'cd #{tmp("gems")} && rackup'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + expect(out).to include("1.0.0") + end + end + + describe "from gems bundled via :path" do + before(:each) do + build_lib "fizz", :path => home("fizz") do |s| + s.executables = "fizz" + end + + install_gemfile <<-G + gem "fizz", :path => "#{File.expand_path(home("fizz"))}" + G + end + + it "works when unlocked" do + bundle "exec fizz" + expect(out).to eq("1.0") + end + + it "works when locked" do + expect(the_bundle).to be_locked + + bundle "exec fizz" + expect(out).to eq("1.0") + end + end + + describe "from gems bundled via :git" do + before(:each) do + build_git "fizz_git" do |s| + s.executables = "fizz_git" + end + + install_gemfile <<-G + gem "fizz_git", :git => "#{lib_path("fizz_git-1.0")}" + G + end + + it "works when unlocked" do + bundle "exec fizz_git" + expect(out).to eq("1.0") + end + + it "works when locked" do + expect(the_bundle).to be_locked + bundle "exec fizz_git" + expect(out).to eq("1.0") + end + end + + describe "from gems bundled via :git with no gemspec" do + before(:each) do + build_git "fizz_no_gemspec", :gemspec => false do |s| + s.executables = "fizz_no_gemspec" + end + + install_gemfile <<-G + gem "fizz_no_gemspec", "1.0", :git => "#{lib_path("fizz_no_gemspec-1.0")}" + G + end + + it "works when unlocked" do + bundle "exec fizz_no_gemspec" + expect(out).to eq("1.0") + end + + it "works when locked" do + expect(the_bundle).to be_locked + bundle "exec fizz_no_gemspec" + expect(out).to eq("1.0") + end + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + gem "foo" + G + + bundle "config auto_install 1" + bundle "exec rackup" + expect(out).to include("Installing foo 1.0") + end + + describe "with gems bundled via :path with invalid gemspecs", :ruby_repo do + it "outputs the gemspec validation errors", :rubygems => ">= 1.7.2" do + build_lib "foo" + + gemspec = lib_path("foo-1.0").join("foo.gemspec").to_s + File.open(gemspec, "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = 'foo' + s.version = '1.0' + s.summary = 'TODO: Add summary' + s.authors = 'Me' + end + G + end + + install_gemfile <<-G + gem "foo", :path => "#{lib_path("foo-1.0")}" + G + + bundle "exec irb" + + expect(err).to match("The gemspec at #{lib_path("foo-1.0").join("foo.gemspec")} is not valid") + expect(err).to match('"TODO" is not a summary') + end + end + + describe "with gems bundled for deployment" do + it "works when calling bundler from another script" do + gemfile <<-G + module Monkey + def bin_path(a,b,c) + raise Gem::GemNotFoundException.new('Fail') + end + end + Bundler.rubygems.extend(Monkey) + G + bundle "install --deployment" + bundle "exec ruby -e '`#{bindir.join("bundler")} -v`; puts $?.success?'", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } + expect(out).to match("true") + end + end + + context "`load`ing a ruby file instead of `exec`ing" do + let(:path) { bundled_app("ruby_executable") } + let(:shebang) { "#!/usr/bin/env ruby" } + let(:executable) { <<-RUBY.gsub(/^ */, "").strip } + #{shebang} + + require "rack" + puts "EXEC: \#{caller.grep(/load/).empty? ? 'exec' : 'load'}" + puts "ARGS: \#{$0} \#{ARGV.join(' ')}" + puts "RACK: \#{RACK}" + process_title = `ps -o args -p \#{Process.pid}`.split("\n", 2).last.strip + puts "PROCESS: \#{process_title}" + RUBY + + before do + path.open("w") {|f| f << executable } + path.chmod(0o755) + + install_gemfile <<-G + gem "rack" + G + end + + let(:exec) { "EXEC: load" } + let(:args) { "ARGS: #{path} arg1 arg2" } + let(:rack) { "RACK: 1.0.0" } + let(:process) do + title = "PROCESS: #{path}" + title += " arg1 arg2" if RUBY_VERSION >= "2.1" + title + end + let(:exit_code) { 0 } + let(:expected) { [exec, args, rack, process].join("\n") } + let(:expected_err) { "" } + + subject { bundle "exec #{path} arg1 arg2", :env => { :RUBYOPT => "-r#{spec_dir.join("support/hax")}" } } + + shared_examples_for "it runs" do + it "like a normally executed executable" do + subject + expect(exitstatus).to eq(exit_code) if exitstatus + expect(last_command.stderr).to eq(expected_err) + expect(last_command.stdout).to eq(expected) + end + end + + it_behaves_like "it runs" + + context "the executable exits explicitly" do + let(:executable) { super() << "\nexit #{exit_code}\nputs 'POST_EXIT'\n" } + + context "with exit 0" do + it_behaves_like "it runs" + end + + context "with exit 99" do + let(:exit_code) { 99 } + it_behaves_like "it runs" + end + end + + context "the executable exits by SignalException" do + let(:executable) do + ex = super() + ex << "\n" + if LessThanProc.with(RUBY_VERSION).call("1.9") + # Ruby < 1.9 needs a flush for a exit by signal, later + # rubies do not + ex << "STDOUT.flush\n" + end + ex << "raise SignalException, 'SIGTERM'\n" + ex + end + let(:expected_err) { ENV["TRAVIS"] ? "Terminated" : "" } + let(:exit_code) do + # signal mask 128 + plus signal 15 -> TERM + # this is specified by C99 + 128 + 15 + end + it_behaves_like "it runs" + end + + context "the executable is empty", :bundler => "< 2" do + let(:executable) { "" } + + let(:exit_code) { 0 } + let(:expected) { "#{path} is empty" } + let(:expected_err) { "" } + if LessThanProc.with(RUBY_VERSION).call("1.9") + # Kernel#exec in ruby < 1.9 will raise Errno::ENOEXEC if the command content is empty, + # even if the command is set as an executable. + pending "Kernel#exec is different" + else + it_behaves_like "it runs" + end + end + + context "the executable is empty", :bundler => "2" do + let(:executable) { "" } + + let(:exit_code) { 0 } + let(:expected_err) { "#{path} is empty" } + let(:expected) { "" } + it_behaves_like "it runs" + end + + context "the executable raises", :bundler => "< 2" do + let(:executable) { super() << "\nraise 'ERROR'" } + let(:exit_code) { 1 } + let(:expected) { super() << "\nbundler: failed to load command: #{path} (#{path})" } + let(:expected_err) do + "RuntimeError: ERROR\n #{path}:10" + + (Bundler.current_ruby.ruby_18? ? "" : ":in `'") + end + it_behaves_like "it runs" + end + + context "the executable raises", :bundler => "2" do + let(:executable) { super() << "\nraise 'ERROR'" } + let(:exit_code) { 1 } + let(:expected_err) do + "bundler: failed to load command: #{path} (#{path})" \ + "\nRuntimeError: ERROR\n #{path}:10:in `'" + end + it_behaves_like "it runs" + end + + context "the executable raises an error without a backtrace", :bundler => "< 2" do + let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" } + let(:exit_code) { 1 } + let(:expected) { super() << "\nbundler: failed to load command: #{path} (#{path})" } + let(:expected_err) { "Err: Err" } + + it_behaves_like "it runs" + end + + context "the executable raises an error without a backtrace", :bundler => "2" do + let(:executable) { super() << "\nclass Err < Exception\ndef backtrace; end;\nend\nraise Err" } + let(:exit_code) { 1 } + let(:expected_err) { "bundler: failed to load command: #{path} (#{path})\nErr: Err" } + let(:expected) { super() } + + it_behaves_like "it runs" + end + + context "when the file uses the current ruby shebang", :ruby_repo do + let(:shebang) { "#!#{Gem.ruby}" } + it_behaves_like "it runs" + end + + context "when Bundler.setup fails", :bundler => "< 2" do + before do + gemfile <<-G + gem 'rack', '2' + G + ENV["BUNDLER_FORCE_TTY"] = "true" + end + + let(:exit_code) { Bundler::GemNotFound.new.status_code } + let(:expected) { <<-EOS.strip } +\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m +\e[33mRun `bundle install` to install missing gems.\e[0m + EOS + + it_behaves_like "it runs" + end + + context "when Bundler.setup fails", :bundler => "2" do + before do + gemfile <<-G + gem 'rack', '2' + G + ENV["BUNDLER_FORCE_TTY"] = "true" + end + + let(:exit_code) { Bundler::GemNotFound.new.status_code } + let(:expected) { <<-EOS.strip } +\e[31mCould not find gem 'rack (= 2)' in locally installed gems. +The source contains 'rack' at: 1.0.0\e[0m +\e[33mRun `bundle install` to install missing gems.\e[0m + EOS + + it_behaves_like "it runs" + end + + context "when the executable exits non-zero via at_exit" do + let(:executable) { super() + "\n\nat_exit { $! ? raise($!) : exit(1) }" } + let(:exit_code) { 1 } + + it_behaves_like "it runs" + end + + context "when disable_exec_load is set" do + let(:exec) { "EXEC: exec" } + let(:process) { "PROCESS: ruby #{path} arg1 arg2" } + + before do + bundle "config disable_exec_load true" + end + + it_behaves_like "it runs" + end + + context "regarding $0 and __FILE__" do + let(:executable) { super() + <<-'RUBY' } + + puts "$0: #{$0.inspect}" + puts "__FILE__: #{__FILE__.inspect}" + RUBY + + let(:expected) { super() + <<-EOS.chomp } + +$0: #{path.to_s.inspect} +__FILE__: #{path.to_s.inspect} + EOS + + it_behaves_like "it runs" + + context "when the path is relative" do + let(:path) { super().relative_path_from(bundled_app) } + + if LessThanProc.with(RUBY_VERSION).call("1.9") + pending "relative paths have ./ __FILE__" + else + it_behaves_like "it runs" + end + end + + context "when the path is relative with a leading ./" do + let(:path) { Pathname.new("./#{super().relative_path_from(Pathname.pwd)}") } + + if LessThanProc.with(RUBY_VERSION).call("< 1.9") + pending "relative paths with ./ have absolute __FILE__" + else + it_behaves_like "it runs" + end + end + end + + context "signal handling" do + let(:test_signals) do + open3_reserved_signals = %w[CHLD CLD PIPE] + reserved_signals = %w[SEGV BUS ILL FPE VTALRM KILL STOP EXIT] + bundler_signals = %w[INT] + + Signal.list.keys - (bundler_signals + reserved_signals + open3_reserved_signals) + end + + context "signals being trapped by bundler" do + let(:executable) { strip_whitespace <<-RUBY } + #{shebang} + begin + Thread.new do + puts 'Started' # For process sync + STDOUT.flush + sleep 1 # ignore quality_spec + raise "Didn't receive INT at all" + end.join + rescue Interrupt + puts "foo" + end + RUBY + + it "receives the signal", :ruby => ">= 1.9.3" do + bundle!("exec #{path}") do |_, o, thr| + o.gets # Consumes 'Started' and ensures that thread has started + Process.kill("INT", thr.pid) + end + + expect(out).to eq("foo") + end + end + + context "signals not being trapped by bunder" do + let(:executable) { strip_whitespace <<-RUBY } + #{shebang} + + signals = #{test_signals.inspect} + result = signals.map do |sig| + Signal.trap(sig, "IGNORE") + end + puts result.select { |ret| ret == "IGNORE" }.count + RUBY + + it "makes sure no unexpected signals are restored to DEFAULT" do + test_signals.each do |n| + Signal.trap(n, "IGNORE") + end + + bundle!("exec #{path}") + + expect(out).to eq(test_signals.count.to_s) + end + end + end + end + + context "nested bundle exec" do + let(:system_gems_to_install) { super() << :bundler } + + context "with shared gems disabled" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle :install, :system_bundler => true, :path => "vendor/bundler" + end + + it "overrides disable_shared_gems so bundler can be found" do + skip "bundler 1.16.x is not support with Ruby 2.6 on Travis CI" if RUBY_VERSION >= "2.6" + + file = bundled_app("file_that_bundle_execs.rb") + create_file(file, <<-RB) + #!#{Gem.ruby} + puts `bundle exec echo foo` + RB + file.chmod(0o777) + bundle! "exec #{file}", :system_bundler => true + expect(out).to eq("foo") + end + end + + context "with a system gem that shadows a default gem" do + let(:openssl_version) { "99.9.9" } + let(:expected) { ruby "gem 'openssl', '< 999999'; require 'openssl'; puts OpenSSL::VERSION", :artifice => nil } + + it "only leaves the default gem in the stdlib available" do + skip "openssl isn't a default gem" if expected.empty? + + install_gemfile! "" # must happen before installing the broken system gem + + build_repo4 do + build_gem "openssl", openssl_version do |s| + s.write("lib/openssl.rb", <<-RB) + raise "custom openssl should not be loaded, it's not in the gemfile!" + RB + end + end + + system_gems(:bundler, "openssl-#{openssl_version}", :gem_repo => gem_repo4) + + file = bundled_app("require_openssl.rb") + create_file(file, <<-RB) + #!/usr/bin/env ruby + require "openssl" + puts OpenSSL::VERSION + warn Gem.loaded_specs.values.map(&:full_name) + RB + file.chmod(0o777) + + aggregate_failures do + expect(bundle!("exec #{file}", :artifice => nil)).to eq(expected) + expect(bundle!("exec bundle exec #{file}", :artifice => nil)).to eq(expected) + expect(bundle!("exec ruby #{file}", :artifice => nil)).to eq(expected) + # Ignore expectaion for default bundler gem conflict. + unless ENV["BUNDLER_SPEC_SUB_VERSION"] + expect(run!(file.read, :artifice => nil)).to eq(expected) + end + end + + # sanity check that we get the newer, custom version without bundler + sys_exec("#{Gem.ruby} #{file}") + expect(last_command.stderr).to include("custom openssl should not be loaded") + end + end + end +end diff --git a/spec/bundler/commands/help_spec.rb b/spec/bundler/commands/help_spec.rb new file mode 100644 index 0000000000..56b1b6f722 --- /dev/null +++ b/spec/bundler/commands/help_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +RSpec.describe "bundle help" do + # RubyGems 1.4+ no longer load gem plugins so this test is no longer needed + it "complains if older versions of bundler are installed", :rubygems => "< 1.4" do + system_gems "bundler-0.8.1" + + bundle "help" + expect(err).to include("older than 0.9") + expect(err).to include("running `gem cleanup bundler`.") + end + + it "uses mann when available" do + with_fake_man do + bundle "help gemfile" + end + expect(out).to eq(%(["#{root}/man/gemfile.5"])) + end + + it "prefixes bundle commands with bundle- when finding the groff files" do + with_fake_man do + bundle "help install" + end + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) + end + + it "simply outputs the txt file when there is no man on the path" do + with_path_as("") do + bundle "help install" + end + expect(out).to match(/BUNDLE-INSTALL/) + end + + it "still outputs the old help for commands that do not have man pages yet" do + bundle "help version" + expect(out).to include("Prints the bundler's version information") + end + + it "looks for a binary and executes it with --help option if it's named bundler-" do + File.open(tmp("bundler-testtasks"), "w", 0o755) do |f| + f.puts "#!/usr/bin/env ruby\nputs ARGV.join(' ')\n" + end + + with_path_added(tmp) do + bundle "help testtasks" + end + + expect(exitstatus).to be_zero if exitstatus + expect(out).to eq("--help") + end + + it "is called when the --help flag is used after the command" do + with_fake_man do + bundle "install --help" + end + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) + end + + it "is called when the --help flag is used before the command" do + with_fake_man do + bundle "--help install" + end + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) + end + + it "is called when the -h flag is used before the command" do + with_fake_man do + bundle "-h install" + end + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) + end + + it "is called when the -h flag is used after the command" do + with_fake_man do + bundle "install -h" + end + expect(out).to eq(%(["#{root}/man/bundle-install.1"])) + end + + it "has helpful output when using --help flag for a non-existent command" do + with_fake_man do + bundle "instill -h" + end + expect(out).to include('Could not find command "instill".') + end + + it "is called when only using the --help flag" do + with_fake_man do + bundle "--help" + end + expect(out).to eq(%(["#{root}/man/bundle.1"])) + + with_fake_man do + bundle "-h" + end + expect(out).to eq(%(["#{root}/man/bundle.1"])) + end +end diff --git a/spec/bundler/commands/info_spec.rb b/spec/bundler/commands/info_spec.rb new file mode 100644 index 0000000000..a9ab8fc210 --- /dev/null +++ b/spec/bundler/commands/info_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +RSpec.describe "bundle info" do + context "info from specific gem in gemfile" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + end + + it "prints information about the current gem" do + bundle "info rails" + expect(out).to include "* rails (2.3.2) +\tSummary: This is just a fake gem for testing +\tHomepage: http://example.com" + expect(out).to match(%r{Path\: .*\/rails\-2\.3\.2}) + end + + context "given a gem that is not installed" do + it "prints missing gem error" do + bundle "info foo" + expect(out).to eq "Could not find gem 'foo'." + end + end + + context "given a default gem shippped in ruby", :ruby_repo do + it "prints information about the default gem", :if => (RUBY_VERSION >= "2.0") do + bundle "info rdoc" + expect(out).to include("* rdoc") + expect(out).to include("Default Gem: yes") + end + end + + context "when gem does not have homepage" do + before do + build_repo1 do + build_gem "rails", "2.3.2" do |s| + s.executables = "rails" + s.summary = "Just another test gem" + end + end + end + + it "excludes the homepage field from the output" do + expect(out).to_not include("Homepage:") + end + end + + context "given --path option" do + it "prints the path to the gem" do + bundle "info rails" + expect(out).to match(%r{.*\/rails\-2\.3\.2}) + end + end + end +end diff --git a/spec/bundler/commands/init_spec.rb b/spec/bundler/commands/init_spec.rb new file mode 100644 index 0000000000..9b5bd95814 --- /dev/null +++ b/spec/bundler/commands/init_spec.rb @@ -0,0 +1,181 @@ +# frozen_string_literal: true + +RSpec.describe "bundle init" do + it "generates a Gemfile", :bundler => "< 2" do + bundle! :init + expect(out).to include("Writing new Gemfile") + expect(bundled_app("Gemfile")).to be_file + end + + it "generates a gems.rb", :bundler => "2" do + bundle! :init + expect(out).to include("Writing new gems.rb") + expect(bundled_app("gems.rb")).to be_file + end + + context "when a Gemfile already exists", :bundler => "< 2" do + before do + create_file "Gemfile", <<-G + gem "rails" + G + end + + it "does not change existing Gemfiles" do + expect { bundle :init }.not_to change { File.read(bundled_app("Gemfile")) } + end + + it "notifies the user that an existing Gemfile already exists" do + bundle :init + expect(out).to include("Gemfile already exists") + end + end + + context "when gems.rb already exists", :bundler => ">= 2" do + before do + create_file("gems.rb", <<-G) + gem "rails" + G + end + + it "does not change existing Gemfiles" do + expect { bundle :init }.not_to change { File.read(bundled_app("gems.rb")) } + end + + it "notifies the user that an existing gems.rb already exists" do + bundle :init + expect(out).to include("gems.rb already exists") + end + end + + context "when a Gemfile exists in a parent directory", :bundler => "< 2" do + let(:subdir) { "child_dir" } + + it "lets users generate a Gemfile in a child directory" do + bundle! :init + + FileUtils.mkdir bundled_app(subdir) + + Dir.chdir bundled_app(subdir) do + bundle! :init + end + + expect(out).to include("Writing new Gemfile") + expect(bundled_app("#{subdir}/Gemfile")).to be_file + end + end + + context "when the dir is not writable by the current user" do + let(:subdir) { "child_dir" } + + it "notifies the user that it can not write to it" do + FileUtils.mkdir bundled_app(subdir) + # chmod a-w it + mode = File.stat(bundled_app(subdir)).mode ^ 0o222 + FileUtils.chmod mode, bundled_app(subdir) + + Dir.chdir bundled_app(subdir) do + bundle :init + end + + expect(out).to include("directory is not writable") + expect(Dir[bundled_app("#{subdir}/*")]).to be_empty + end + end + + context "when a gems.rb file exists in a parent directory", :bundler => ">= 2" do + let(:subdir) { "child_dir" } + + it "lets users generate a Gemfile in a child directory" do + bundle! :init + + FileUtils.mkdir bundled_app(subdir) + + Dir.chdir bundled_app(subdir) do + bundle! :init + end + + expect(out).to include("Writing new gems.rb") + expect(bundled_app("#{subdir}/gems.rb")).to be_file + end + end + + context "given --gemspec option", :bundler => "< 2" do + let(:spec_file) { tmp.join("test.gemspec") } + + it "should generate from an existing gemspec" do + File.open(spec_file, "w") do |file| + file << <<-S + Gem::Specification.new do |s| + s.name = 'test' + s.add_dependency 'rack', '= 1.0.1' + s.add_development_dependency 'rspec', '1.2' + end + S + end + + bundle :init, :gemspec => spec_file + + gemfile = if Bundler::VERSION[0, 2] == "1." + bundled_app("Gemfile").read + else + bundled_app("gems.rb").read + end + expect(gemfile).to match(%r{source 'https://rubygems.org'}) + expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1) + expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1) + expect(gemfile.scan(/group :development/).size).to eq(1) + end + + context "when gemspec file is invalid" do + it "notifies the user that specification is invalid" do + File.open(spec_file, "w") do |file| + file << <<-S + Gem::Specification.new do |s| + s.name = 'test' + s.invalid_method_name + end + S + end + + bundle :init, :gemspec => spec_file + expect(last_command.bundler_err).to include("There was an error while loading `test.gemspec`") + end + end + end + + context "when init_gems_rb setting is enabled" do + before { bundle "config init_gems_rb true" } + + context "given --gemspec option", :bundler => "< 2" do + let(:spec_file) { tmp.join("test.gemspec") } + + before do + File.open(spec_file, "w") do |file| + file << <<-S + Gem::Specification.new do |s| + s.name = 'test' + s.add_dependency 'rack', '= 1.0.1' + s.add_development_dependency 'rspec', '1.2' + end + S + end + end + + it "should generate from an existing gemspec" do + bundle :init, :gemspec => spec_file + + gemfile = bundled_app("gems.rb").read + expect(gemfile).to match(%r{source 'https://rubygems.org'}) + expect(gemfile.scan(/gem "rack", "= 1.0.1"/).size).to eq(1) + expect(gemfile.scan(/gem "rspec", "= 1.2"/).size).to eq(1) + expect(gemfile.scan(/group :development/).size).to eq(1) + end + + it "prints message to user" do + bundle :init, :gemspec => spec_file + + expect(out).to include("Writing new gems.rb") + end + end + end +end diff --git a/spec/bundler/commands/inject_spec.rb b/spec/bundler/commands/inject_spec.rb new file mode 100644 index 0000000000..b7ffc89a34 --- /dev/null +++ b/spec/bundler/commands/inject_spec.rb @@ -0,0 +1,117 @@ +# frozen_string_literal: true + +RSpec.describe "bundle inject", :bundler => "< 2" do + before :each do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + context "without a lockfile" do + it "locks with the injected gems" do + expect(bundled_app("Gemfile.lock")).not_to exist + bundle "inject 'rack-obama' '> 0'" + expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/) + end + end + + context "with a lockfile" do + before do + bundle "install" + end + + it "adds the injected gems to the Gemfile" do + expect(bundled_app("Gemfile").read).not_to match(/rack-obama/) + bundle "inject 'rack-obama' '> 0'" + expect(bundled_app("Gemfile").read).to match(/rack-obama/) + end + + it "locks with the injected gems" do + expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/) + bundle "inject 'rack-obama' '> 0'" + expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/) + end + end + + context "with injected gems already in the Gemfile" do + it "doesn't add existing gems" do + bundle "inject 'rack' '> 0'" + expect(out).to match(/cannot specify the same gem twice/i) + end + end + + context "incorrect arguments" do + it "fails when more than 2 arguments are passed" do + bundle "inject gem_name 1 v" + expect(out).to eq(<<-E.strip) +ERROR: "bundle inject" was called with arguments ["gem_name", "1", "v"] +Usage: "bundle inject GEM VERSION" + E + end + end + + context "with source option" do + it "add gem with source option in gemfile" do + bundle "inject 'foo' '>0' --source file://#{gem_repo1}" + gemfile = bundled_app("Gemfile").read + str = "gem \"foo\", \"> 0\", :source => \"file://#{gem_repo1}\"" + expect(gemfile).to include str + end + end + + context "with group option" do + it "add gem with group option in gemfile" do + bundle "inject 'rack-obama' '>0' --group=development" + gemfile = bundled_app("Gemfile").read + str = "gem \"rack-obama\", \"> 0\", :group => :development" + expect(gemfile).to include str + end + + it "add gem with multiple groups in gemfile" do + bundle "inject 'rack-obama' '>0' --group=development,test" + gemfile = bundled_app("Gemfile").read + str = "gem \"rack-obama\", \"> 0\", :groups => [:development, :test]" + expect(gemfile).to include str + end + end + + context "when frozen" do + before do + bundle "install" + if Bundler.feature_flag.bundler_2_mode? + bundle! "config --local deployment true" + else + bundle! "config --local frozen true" + end + end + + it "injects anyway" do + bundle "inject 'rack-obama' '> 0'" + expect(bundled_app("Gemfile").read).to match(/rack-obama/) + end + + it "locks with the injected gems" do + expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/) + bundle "inject 'rack-obama' '> 0'" + expect(bundled_app("Gemfile.lock").read).to match(/rack-obama/) + end + + it "restores frozen afterwards" do + bundle "inject 'rack-obama' '> 0'" + config = YAML.load(bundled_app(".bundle/config").read) + expect(config["BUNDLE_DEPLOYMENT"] || config["BUNDLE_FROZEN"]).to eq("true") + end + + it "doesn't allow Gemfile changes" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack-obama" + G + bundle "inject 'rack' '> 0'" + expect(out).to match(/trying to install in deployment mode after changing/) + + expect(bundled_app("Gemfile.lock").read).not_to match(/rack-obama/) + end + end +end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb new file mode 100644 index 0000000000..394f672fef --- /dev/null +++ b/spec/bundler/commands/install_spec.rb @@ -0,0 +1,587 @@ +# frozen_string_literal: true + +RSpec.describe "bundle install with gem sources" do + describe "the simple case" do + it "prints output and returns if no dependencies are specified" do + gemfile <<-G + source "file://#{gem_repo1}" + G + + bundle :install + expect(out).to match(/no dependencies/) + end + + it "does not make a lockfile if the install fails" do + install_gemfile <<-G + raise StandardError, "FAIL" + G + + expect(last_command.bundler_err).to include('StandardError, "FAIL"') + expect(bundled_app("Gemfile.lock")).not_to exist + end + + it "creates a Gemfile.lock" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + expect(bundled_app("Gemfile.lock")).to exist + end + + it "does not create ./.bundle by default", :bundler => "< 2" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle! :install # can't use install_gemfile since it sets retry + expect(bundled_app(".bundle")).not_to exist + end + + it "does not create ./.bundle by default when installing to system gems" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + bundle! :install, :env => { "BUNDLE_PATH__SYSTEM" => true } # can't use install_gemfile since it sets retry + expect(bundled_app(".bundle")).not_to exist + end + + it "creates lock files based on the Gemfile name" do + gemfile bundled_app("OmgFile"), <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0" + G + + bundle "install --gemfile OmgFile" + + expect(bundled_app("OmgFile.lock")).to exist + end + + it "doesn't delete the lockfile if one already exists" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + lockfile = File.read(bundled_app("Gemfile.lock")) + + install_gemfile <<-G + raise StandardError, "FAIL" + G + + expect(File.read(bundled_app("Gemfile.lock"))).to eq(lockfile) + end + + it "does not touch the lockfile if nothing changed" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + expect { run "1" }.not_to change { File.mtime(bundled_app("Gemfile.lock")) } + end + + it "fetches gems" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + expect(default_bundle_path("gems/rack-1.0.0")).to exist + expect(the_bundle).to include_gems("rack 1.0.0") + end + + it "fetches gems when multiple versions are specified" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack', "> 0.9", "< 1.0" + G + + expect(default_bundle_path("gems/rack-0.9.1")).to exist + expect(the_bundle).to include_gems("rack 0.9.1") + end + + it "fetches gems when multiple versions are specified take 2" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack', "< 1.0", "> 0.9" + G + + expect(default_bundle_path("gems/rack-0.9.1")).to exist + expect(the_bundle).to include_gems("rack 0.9.1") + end + + it "raises an appropriate error when gems are specified using symbols" do + install_gemfile(<<-G) + source "file://#{gem_repo1}" + gem :rack + G + expect(exitstatus).to eq(4) if exitstatus + end + + it "pulls in dependencies" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + expect(the_bundle).to include_gems "actionpack 2.3.2", "rails 2.3.2" + end + + it "does the right version" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + G + + expect(the_bundle).to include_gems "rack 0.9.1" + end + + it "does not install the development dependency" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "with_development_dependency" + G + + expect(the_bundle).to include_gems("with_development_dependency 1.0.0"). + and not_include_gems("activesupport 2.3.5") + end + + it "resolves correctly" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activemerchant" + gem "rails" + G + + expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2" + end + + it "activates gem correctly according to the resolved gems" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport", "2.3.5" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activemerchant" + gem "rails" + G + + expect(the_bundle).to include_gems "activemerchant 1.0", "activesupport 2.3.2", "actionpack 2.3.2" + end + + it "does not reinstall any gem that is already available locally" do + system_gems "activesupport-2.3.2", :path => :bundle_path + + build_repo2 do + build_gem "activesupport", "2.3.2" do |s| + s.write "lib/activesupport.rb", "ACTIVESUPPORT = 'fail'" + end + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activerecord", "2.3.2" + G + + expect(the_bundle).to include_gems "activesupport 2.3.2" + end + + it "works when the gemfile specifies gems that only exist in the system" do + build_gem "foo", :to_bundle => true + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "foo" + G + + expect(the_bundle).to include_gems "rack 1.0.0", "foo 1.0.0" + end + + it "prioritizes local gems over remote gems" do + build_gem "rack", "1.0.0", :to_bundle => true do |s| + s.add_dependency "activesupport", "2.3.5" + end + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + + expect(the_bundle).to include_gems "rack 1.0.0", "activesupport 2.3.5" + end + + describe "with a gem that installs multiple platforms" do + it "installs gems for the local platform as first choice" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 #{Bundler.local_platform}") + end + + it "falls back on plain ruby" do + simulate_platform "foo-bar-baz" + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 RUBY") + end + + it "installs gems for java" do + simulate_platform "java" + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 JAVA") + end + + it "installs gems for windows" do + simulate_platform mswin + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 MSWIN") + end + end + + describe "doing bundle install foo" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + end + + it "works" do + bundle "install", forgotten_command_line_options(:path => "vendor") + expect(the_bundle).to include_gems "rack 1.0" + end + + it "allows running bundle install --system without deleting foo", :bundler => "< 2" do + bundle "install", forgotten_command_line_options(:path => "vendor") + bundle "install", forgotten_command_line_options(:system => true) + FileUtils.rm_rf(bundled_app("vendor")) + expect(the_bundle).to include_gems "rack 1.0" + end + + it "allows running bundle install --system after deleting foo", :bundler => "< 2" do + bundle "install", forgotten_command_line_options(:path => "vendor") + FileUtils.rm_rf(bundled_app("vendor")) + bundle "install", forgotten_command_line_options(:system => true) + expect(the_bundle).to include_gems "rack 1.0" + end + end + + it "finds gems in multiple sources", :bundler => "< 2" do + build_repo2 + update_repo2 + + install_gemfile <<-G + source "file://#{gem_repo1}" + source "file://#{gem_repo2}" + + gem "activesupport", "1.2.3" + gem "rack", "1.2" + G + + expect(the_bundle).to include_gems "rack 1.2", "activesupport 1.2.3" + end + + it "gives a useful error if no sources are set" do + install_gemfile <<-G + gem "rack" + G + + bundle :install + expect(out).to include("Your Gemfile has no gem server sources") + end + + it "creates a Gemfile.lock on a blank Gemfile" do + install_gemfile <<-G + G + + expect(File.exist?(bundled_app("Gemfile.lock"))).to eq(true) + end + + context "throws a warning if a gem is added twice in Gemfile" do + it "without version requirements" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + gem "rack" + G + + expect(out).to include("Your Gemfile lists the gem rack (>= 0) more than once.") + expect(out).to include("Remove any duplicate entries and specify the gem only once (per group).") + expect(out).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") + end + + it "with same versions" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack", "1.0" + gem "rack", "1.0" + G + + expect(out).to include("Your Gemfile lists the gem rack (= 1.0) more than once.") + expect(out).to include("Remove any duplicate entries and specify the gem only once (per group).") + expect(out).to include("While it's not a problem now, it could cause errors if you change the version of one of them later.") + end + end + + context "throws an error if a gem is added twice in Gemfile" do + it "when version of one dependency is not specified" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + gem "rack", "1.0" + G + + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).to include("You specified: rack (>= 0) and rack (= 1.0).") + end + + it "when different versions of both dependencies are specified" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack", "1.0" + gem "rack", "1.1" + G + + expect(out).to include("You cannot specify the same gem twice with different version requirements") + expect(out).to include("You specified: rack (= 1.0) and rack (= 1.1).") + end + end + + it "gracefully handles error when rubygems server is unavailable" do + install_gemfile <<-G, :artifice => nil + source "file://#{gem_repo1}" + source "http://localhost:9384" do + gem 'foo' + end + G + + bundle :install, :artifice => nil + expect(out).to include("Could not fetch specs from http://localhost:9384/") + expect(out).not_to include("file://") + end + + it "fails gracefully when downloading an invalid specification from the full index", :rubygems => "2.5" do + build_repo2 do + build_gem "ajp-rails", "0.0.0", :gemspec => false, :skip_validation => true do |s| + bad_deps = [["ruby-ajp", ">= 0.2.0"], ["rails", ">= 0.14"]] + s. + instance_variable_get(:@spec). + instance_variable_set(:@dependencies, bad_deps) + + raise "failed to set bad deps" unless s.dependencies == bad_deps + end + build_gem "ruby-ajp", "1.0.0" + end + + install_gemfile <<-G, :full_index => true + source "file:\/\/localhost#{gem_repo2}" + + gem "ajp-rails", "0.0.0" + G + + expect(last_command.stdboth).not_to match(/Error Report/i) + expect(last_command.bundler_err).to include("An error occurred while installing ajp-rails (0.0.0), and Bundler cannot continue."). + and include(normalize_uri_file("Make sure that `gem install ajp-rails -v '0.0.0' --source 'file://localhost#{gem_repo2}/'` succeeds before bundling.")) + end + + it "doesn't blow up when the local .bundle/config is empty" do + FileUtils.mkdir_p(bundled_app(".bundle")) + FileUtils.touch(bundled_app(".bundle/config")) + + install_gemfile(<<-G) + source "file://#{gem_repo1}" + + gem 'foo' + G + expect(exitstatus).to eq(0) if exitstatus + end + + it "doesn't blow up when the global .bundle/config is empty" do + FileUtils.mkdir_p("#{Bundler.rubygems.user_home}/.bundle") + FileUtils.touch("#{Bundler.rubygems.user_home}/.bundle/config") + + install_gemfile(<<-G) + source "file://#{gem_repo1}" + + gem 'foo' + G + expect(exitstatus).to eq(0) if exitstatus + end + end + + describe "Ruby version in Gemfile.lock" do + include Bundler::GemHelpers + + context "and using an unsupported Ruby version" do + it "prints an error" do + install_gemfile <<-G + ::RUBY_VERSION = '2.0.1' + ruby '~> 2.2' + G + expect(out).to include("Your Ruby version is 2.0.1, but your Gemfile specified ~> 2.2") + end + end + + context "and using a supported Ruby version" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '2.1.3' + ::RUBY_PATCHLEVEL = 100 + ruby '~> 2.1.0' + G + end + + it "writes current Ruby version to Gemfile.lock" do + lockfile_should_be <<-L + GEM + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + RUBY VERSION + ruby 2.1.3p100 + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "updates Gemfile.lock with updated incompatible ruby version" do + install_gemfile <<-G + ::RUBY_VERSION = '2.2.3' + ::RUBY_PATCHLEVEL = 100 + ruby '~> 2.2.0' + G + + lockfile_should_be <<-L + GEM + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + RUBY VERSION + ruby 2.2.3p100 + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + end + + describe "when Bundler root contains regex chars" do + before do + root_dir = tmp("foo[]bar") + + FileUtils.mkdir_p(root_dir) + in_app_root_custom(root_dir) + end + + it "doesn't blow up" do + build_lib "foo" + gemfile = <<-G + gem 'foo', :path => "#{lib_path("foo-1.0")}" + G + File.open("Gemfile", "w") do |file| + file.puts gemfile + end + + bundle :install + + expect(exitstatus).to eq(0) if exitstatus + end + end + + describe "when requesting a quiet install via --quiet" do + it "should be quiet" do + gemfile <<-G + gem 'rack' + G + + bundle :install, :quiet => true + expect(out).to include("Could not find gem 'rack'") + expect(out).to_not include("Your Gemfile has no gem server sources") + end + end + + describe "when bundle path does not have write access" do + before do + FileUtils.mkdir_p(bundled_app("vendor")) + gemfile <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + end + + it "should display a proper message to explain the problem" do + FileUtils.chmod(0o500, bundled_app("vendor")) + + bundle :install, forgotten_command_line_options(:path => "vendor") + expect(out).to include(bundled_app("vendor").to_s) + expect(out).to include("grant write permissions") + end + end + + context "after installing with --standalone" do + before do + install_gemfile! <<-G + source "file://#{gem_repo1}" + gem "rack" + G + forgotten_command_line_options(:path => "bundle") + bundle! "install", :standalone => true + end + + it "includes the standalone path" do + bundle! "binstubs rack", :standalone => true + standalone_line = File.read(bundled_app("bin/rackup")).each_line.find {|line| line.include? "$:.unshift" }.strip + expect(standalone_line).to eq %($:.unshift File.expand_path "../../bundle", path.realpath) + end + end + + describe "when bundle install is executed with unencoded authentication" do + before do + gemfile <<-G + source 'https://rubygems.org/' + gem "." + G + end + + it "should display a helpful messag explaining how to fix it" do + bundle :install, :env => { "BUNDLE_RUBYGEMS__ORG" => "user:pass{word" } + expect(exitstatus).to eq(17) if exitstatus + expect(out).to eq("Please CGI escape your usernames and passwords before " \ + "setting them for authentication.") + end + end +end diff --git a/spec/bundler/commands/issue_spec.rb b/spec/bundler/commands/issue_spec.rb new file mode 100644 index 0000000000..04c575130e --- /dev/null +++ b/spec/bundler/commands/issue_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +RSpec.describe "bundle issue" do + it "exits with a message" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + bundle "issue" + expect(out).to include "Did you find an issue with Bundler?" + expect(out).to include "## Environment" + expect(out).to include "## Gemfile" + expect(out).to include "## Bundle Doctor" + end +end diff --git a/spec/bundler/commands/licenses_spec.rb b/spec/bundler/commands/licenses_spec.rb new file mode 100644 index 0000000000..d61d3492f3 --- /dev/null +++ b/spec/bundler/commands/licenses_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +RSpec.describe "bundle licenses" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "with_license" + G + end + + it "prints license information for all gems in the bundle" do + bundle "licenses" + + loaded_bundler_spec = Bundler.load.specs["bundler"] + expected = if !loaded_bundler_spec.empty? + loaded_bundler_spec[0].license + else + "Unknown" + end + + expect(out).to include("bundler: #{expected}") + expect(out).to include("with_license: MIT") + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "with_license" + gem "foo" + G + + bundle "config auto_install 1" + bundle :licenses + expect(out).to include("Installing foo 1.0") + end +end diff --git a/spec/bundler/commands/list_spec.rb b/spec/bundler/commands/list_spec.rb new file mode 100644 index 0000000000..5305176c65 --- /dev/null +++ b/spec/bundler/commands/list_spec.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +RSpec.describe "bundle list", :bundler => "2" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + gem "rspec", :group => [:test] + G + end + + context "with name-only and paths option" do + it "raises an error" do + bundle "list --name-only --paths" + + expect(out).to eq "The `--name-only` and `--paths` options cannot be used together" + end + end + + context "with without-group and only-group option" do + it "raises an error" do + bundle "list --without-group dev --only-group test" + + expect(out).to eq "The `--only-group` and `--without-group` options cannot be used together" + end + end + + describe "with without-group option" do + context "when group is present" do + it "prints the gems not in the specified group" do + bundle! "list --without-group test" + + expect(out).to include(" * rack (1.0.0)") + expect(out).not_to include(" * rspec (1.2.7)") + end + end + + context "when group is not found" do + it "raises an error" do + bundle "list --without-group random" + + expect(out).to eq "`random` group could not be found." + end + end + end + + describe "with only-group option" do + context "when group is present" do + it "prints the gems in the specified group" do + bundle! "list --only-group default" + + expect(out).to include(" * rack (1.0.0)") + expect(out).not_to include(" * rspec (1.2.7)") + end + end + + context "when group is not found" do + it "raises an error" do + bundle "list --only-group random" + + expect(out).to eq "`random` group could not be found." + end + end + end + + context "with name-only option" do + it "prints only the name of the gems in the bundle" do + bundle "list --name-only" + + expect(out).to include("rack") + expect(out).to include("rspec") + end + end + + context "with paths option" do + before do + build_repo2 do + build_gem "bar" + end + + build_git "git_test", "1.0.0", :path => lib_path("git_test") + + build_lib("gemspec_test", :path => tmp.join("gemspec_test")) do |s| + s.write("Gemfile", "source :rubygems\ngemspec") + s.add_dependency "bar", "=1.0.0" + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + gem "rails" + gem "git_test", :git => "#{lib_path("git_test")}" + gemspec :path => "#{tmp.join("gemspec_test")}" + G + + bundle! "install" + end + + it "prints the path of each gem in the bundle" do + bundle "list --paths" + expect(out).to match(%r{.*\/rails\-2\.3\.2}) + expect(out).to match(%r{.*\/rack\-1\.2}) + expect(out).to match(%r{.*\/git_test\-\w}) + expect(out).to match(%r{.*\/gemspec_test}) + end + end + + context "when no gems are in the gemfile" do + before do + install_gemfile <<-G + source "file://#{gem_repo1}" + G + end + + it "prints message saying no gems are in the bundle" do + bundle "list" + expect(out).to include("No gems in the Gemfile") + end + end + + it "lists gems installed in the bundle" do + bundle "list" + expect(out).to include(" * rack (1.0.0)") + end + + it "aliases the ls command to list" do + bundle "ls" + expect(out).to include("Gems included by the bundle") + end +end diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb new file mode 100644 index 0000000000..0b77605f01 --- /dev/null +++ b/spec/bundler/commands/lock_spec.rb @@ -0,0 +1,322 @@ +# frozen_string_literal: true + +RSpec.describe "bundle lock" do + def strip_lockfile(lockfile) + strip_whitespace(lockfile).sub(/\n\Z/, "") + end + + def read_lockfile(file = "Gemfile.lock") + strip_lockfile bundled_app(file).read + end + + let(:repo) { gem_repo1 } + + before :each do + gemfile <<-G + source "file://localhost#{repo}" + gem "rails" + gem "with_license" + gem "foo" + G + + @lockfile = strip_lockfile(normalize_uri_file(<<-L)) + GEM + remote: file://localhost#{repo}/ + specs: + actionmailer (2.3.2) + activesupport (= 2.3.2) + actionpack (2.3.2) + activesupport (= 2.3.2) + activerecord (2.3.2) + activesupport (= 2.3.2) + activeresource (2.3.2) + activesupport (= 2.3.2) + activesupport (2.3.2) + foo (1.0) + rails (2.3.2) + actionmailer (= 2.3.2) + actionpack (= 2.3.2) + activerecord (= 2.3.2) + activeresource (= 2.3.2) + rake (= 10.0.2) + rake (10.0.2) + with_license (1.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + foo + rails + with_license + + BUNDLED WITH + #{Bundler::VERSION} + L + end + + it "prints a lockfile when there is no existing lockfile with --print" do + bundle "lock --print" + + expect(out).to eq(@lockfile) + end + + it "prints a lockfile when there is an existing lockfile with --print" do + lockfile @lockfile + + bundle "lock --print" + + expect(out).to eq(@lockfile) + end + + it "writes a lockfile when there is no existing lockfile" do + bundle "lock" + + expect(read_lockfile).to eq(@lockfile) + end + + it "writes a lockfile when there is an outdated lockfile using --update" do + lockfile @lockfile.gsub("2.3.2", "2.3.1") + + bundle! "lock --update" + + expect(read_lockfile).to eq(@lockfile) + end + + it "does not fetch remote specs when using the --local option" do + bundle "lock --update --local" + + expect(out).to match(/sources listed in your Gemfile|installed locally/) + end + + it "writes to a custom location using --lockfile" do + bundle "lock --lockfile=lock" + + expect(out).to match(/Writing lockfile to.+lock/) + expect(read_lockfile "lock").to eq(@lockfile) + expect { read_lockfile }.to raise_error(Errno::ENOENT) + end + + it "writes to custom location using --lockfile when a default lockfile is present" do + bundle "install" + bundle "lock --lockfile=lock" + + expect(out).to match(/Writing lockfile to.+lock/) + expect(read_lockfile("lock")).to eq(@lockfile) + end + + it "update specific gems using --update" do + lockfile @lockfile.gsub("2.3.2", "2.3.1").gsub("10.0.2", "10.0.1") + + bundle "lock --update rails rake" + + expect(read_lockfile).to eq(@lockfile) + end + + it "errors when updating a missing specific gems using --update" do + lockfile @lockfile + + bundle "lock --update blahblah" + expect(out).to eq("Could not find gem 'blahblah'.") + + expect(read_lockfile).to eq(@lockfile) + end + + # see update_spec for more coverage on same options. logic is shared so it's not necessary + # to repeat coverage here. + context "conservative updates" do + before do + build_repo4 do + build_gem "foo", %w[1.4.3 1.4.4] do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w[1.4.5 1.5.0] do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w[1.5.1] do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0] + build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0] + end + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + it "single gem updates dependent gem to minor" do + bundle "lock --update foo --patch" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.4.5 bar-2.1.1 qux-1.0.0].sort) + end + + it "minor preferred with strict" do + bundle "lock --update --minor --strict" + + expect(the_bundle.locked_gems.specs.map(&:full_name)).to eq(%w[foo-1.5.0 bar-2.1.1 qux-1.1.0].sort) + end + end + + it "supports adding new platforms" do + bundle! "lock --add-platform java x86-mingw32" + + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq) + end + + it "supports adding the `ruby` platform" do + bundle! "lock --add-platform ruby" + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to match_array(local_platforms.unshift("ruby").uniq) + end + + it "warns when adding an unknown platform" do + bundle "lock --add-platform foobarbaz" + expect(out).to include("The platform `foobarbaz` is unknown to RubyGems and adding it will likely lead to resolution errors") + end + + it "allows removing platforms" do + bundle! "lock --add-platform java x86-mingw32" + + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to match_array(local_platforms.unshift(java, mingw).uniq) + + bundle! "lock --remove-platform java" + + lockfile = Bundler::LockfileParser.new(read_lockfile) + expect(lockfile.platforms).to match_array(local_platforms.unshift(mingw).uniq) + end + + it "errors when removing all platforms" do + bundle "lock --remove-platform #{local_platforms.join(" ")}" + expect(last_command.bundler_err).to include("Removing all platforms from the bundle is not allowed") + end + + # from https://github.com/bundler/bundler/issues/4896 + it "properly adds platforms when platform requirements come from different dependencies" do + build_repo4 do + build_gem "ffi", "1.9.14" + build_gem "ffi", "1.9.14" do |s| + s.platform = mingw + end + + build_gem "gssapi", "0.1" + build_gem "gssapi", "0.2" + build_gem "gssapi", "0.3" + build_gem "gssapi", "1.2.0" do |s| + s.add_dependency "ffi", ">= 1.0.1" + end + + build_gem "mixlib-shellout", "2.2.6" + build_gem "mixlib-shellout", "2.2.6" do |s| + s.platform = "universal-mingw32" + s.add_dependency "win32-process", "~> 0.8.2" + end + + # we need all these versions to get the sorting the same as it would be + # pulling from rubygems.org + %w[0.8.3 0.8.2 0.8.1 0.8.0].each do |v| + build_gem "win32-process", v do |s| + s.add_dependency "ffi", ">= 1.0.0" + end + end + end + + gemfile <<-G + source "file://localhost#{gem_repo4}" + + gem "mixlib-shellout" + gem "gssapi" + G + + simulate_platform(mingw) { bundle! :lock } + + expect(the_bundle.lockfile).to read_as(normalize_uri_file(strip_whitespace(<<-G))) + GEM + remote: file://localhost#{gem_repo4}/ + specs: + ffi (1.9.14-x86-mingw32) + gssapi (1.2.0) + ffi (>= 1.0.1) + mixlib-shellout (2.2.6-universal-mingw32) + win32-process (~> 0.8.2) + win32-process (0.8.3) + ffi (>= 1.0.0) + + PLATFORMS + x86-mingw32 + + DEPENDENCIES + gssapi + mixlib-shellout + + BUNDLED WITH + #{Bundler::VERSION} + G + + simulate_platform(rb) { bundle! :lock } + + expect(the_bundle.lockfile).to read_as(normalize_uri_file(strip_whitespace(<<-G))) + GEM + remote: file://localhost#{gem_repo4}/ + specs: + ffi (1.9.14) + ffi (1.9.14-x86-mingw32) + gssapi (1.2.0) + ffi (>= 1.0.1) + mixlib-shellout (2.2.6) + mixlib-shellout (2.2.6-universal-mingw32) + win32-process (~> 0.8.2) + win32-process (0.8.3) + ffi (>= 1.0.0) + + PLATFORMS + ruby + x86-mingw32 + + DEPENDENCIES + gssapi + mixlib-shellout + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + context "when an update is available" do + let(:repo) { gem_repo2 } + + before do + lockfile(@lockfile) + build_repo2 do + build_gem "foo", "2.0" + end + end + + it "does not implicitly update" do + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile) + end + + it "accounts for changes in the gemfile" do + gemfile gemfile.gsub('"foo"', '"foo", "2.0"') + bundle! "lock" + + expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)")) + end + end +end diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb new file mode 100644 index 0000000000..e6d6e19122 --- /dev/null +++ b/spec/bundler/commands/newgem_spec.rb @@ -0,0 +1,912 @@ +# frozen_string_literal: true + +RSpec.describe "bundle gem" do + def reset! + super + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" + end + + def remove_push_guard(gem_name) + # Remove exception that prevents public pushes on older RubyGems versions + if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0") + path = "#{gem_name}/#{gem_name}.gemspec" + content = File.read(path).sub(/raise "RubyGems 2\.0 or newer.*/, "") + File.open(path, "w") {|f| f.write(content) } + end + end + + def execute_bundle_gem(gem_name, flag = "", to_remove_push_guard = true) + bundle! "gem #{gem_name} #{flag}" + remove_push_guard(gem_name) if to_remove_push_guard + # reset gemspec cache for each test because of commit 3d4163a + Bundler.clear_gemspec_cache + end + + def gem_skeleton_assertions(gem_name) + expect(bundled_app("#{gem_name}/#{gem_name}.gemspec")).to exist + expect(bundled_app("#{gem_name}/README.md")).to exist + expect(bundled_app("#{gem_name}/Gemfile")).to exist + expect(bundled_app("#{gem_name}/Rakefile")).to exist + expect(bundled_app("#{gem_name}/lib/test/gem.rb")).to exist + expect(bundled_app("#{gem_name}/lib/test/gem/version.rb")).to exist + end + + before do + git_config_content = <<-EOF + [user] + name = "Bundler User" + email = user@example.com + [github] + user = bundleuser + EOF + @git_config_location = ENV["GIT_CONFIG"] + path = "#{File.expand_path(tmp, File.dirname(__FILE__))}/test_git_config.txt" + File.open(path, "w") {|f| f.write(git_config_content) } + ENV["GIT_CONFIG"] = path + end + + after do + FileUtils.rm(ENV["GIT_CONFIG"]) if File.exist?(ENV["GIT_CONFIG"]) + ENV["GIT_CONFIG"] = @git_config_location + end + + shared_examples_for "git config is present" do + context "git config user.{name,email} present" do + it "sets gemspec author to git user.name if available" do + expect(generated_gem.gemspec.authors.first).to eq("Bundler User") + end + + it "sets gemspec email to git user.email if available" do + expect(generated_gem.gemspec.email.first).to eq("user@example.com") + end + end + end + + shared_examples_for "git config is absent" do + it "sets gemspec author to default message if git user.name is not set or empty" do + expect(generated_gem.gemspec.authors.first).to eq("TODO: Write your name") + end + + it "sets gemspec email to default message if git user.email is not set or empty" do + expect(generated_gem.gemspec.email.first).to eq("TODO: Write your email address") + end + end + + shared_examples_for "--mit flag" do + before do + execute_bundle_gem(gem_name, "--mit") + end + it "generates a gem skeleton with MIT license" do + gem_skeleton_assertions(gem_name) + expect(bundled_app("test-gem/LICENSE.txt")).to exist + skel = Bundler::GemHelper.new(bundled_app(gem_name).to_s) + expect(skel.gemspec.license).to eq("MIT") + end + end + + shared_examples_for "--no-mit flag" do + before do + execute_bundle_gem(gem_name, "--no-mit") + end + it "generates a gem skeleton without MIT license" do + gem_skeleton_assertions(gem_name) + expect(bundled_app("test-gem/LICENSE.txt")).to_not exist + end + end + + shared_examples_for "--coc flag" do + before do + execute_bundle_gem(gem_name, "--coc", false) + end + it "generates a gem skeleton with MIT license" do + gem_skeleton_assertions(gem_name) + expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to exist + end + + describe "README additions" do + it "generates the README with a section for the Code of Conduct" do + expect(bundled_app("test-gem/README.md").read).to include("## Code of Conduct") + expect(bundled_app("test-gem/README.md").read).to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") + end + end + end + + shared_examples_for "--no-coc flag" do + before do + execute_bundle_gem(gem_name, "--no-coc", false) + end + it "generates a gem skeleton without Code of Conduct" do + gem_skeleton_assertions(gem_name) + expect(bundled_app("test-gem/CODE_OF_CONDUCT.md")).to_not exist + end + + describe "README additions" do + it "generates the README without a section for the Code of Conduct" do + expect(bundled_app("test-gem/README.md").read).not_to include("## Code of Conduct") + expect(bundled_app("test-gem/README.md").read).not_to include("https://github.com/bundleuser/#{gem_name}/blob/master/CODE_OF_CONDUCT.md") + end + end + end + + context "README.md" do + let(:gem_name) { "test_gem" } + let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) } + + context "git config github.user present" do + before do + execute_bundle_gem(gem_name) + end + + it "contribute URL set to git username" do + expect(bundled_app("test_gem/README.md").read).not_to include("[USERNAME]") + expect(bundled_app("test_gem/README.md").read).to include("github.com/bundleuser") + end + end + + context "git config github.user is absent" do + before do + sys_exec("git config --unset github.user") + reset! + in_app_root + bundle "gem #{gem_name}" + remove_push_guard(gem_name) + end + + it "contribute URL set to [USERNAME]" do + expect(bundled_app("test_gem/README.md").read).to include("[USERNAME]") + expect(bundled_app("test_gem/README.md").read).not_to include("github.com/bundleuser") + end + end + end + + it "creates a new git repository" do + in_app_root + bundle "gem test_gem" + expect(bundled_app("test_gem/.git")).to exist + end + + context "when git is not available" do + let(:gem_name) { "test_gem" } + + # This spec cannot have `git` available in the test env + before do + load_paths = [lib, spec] + load_path_str = "-I#{load_paths.join(File::PATH_SEPARATOR)}" + + sys_exec "PATH=\"\" #{Gem.ruby} #{load_path_str} #{bindir.join("bundle")} gem #{gem_name}" + end + + it "creates the gem without the need for git" do + expect(bundled_app("#{gem_name}/README.md")).to exist + end + + it "doesn't create a git repo" do + expect(bundled_app("#{gem_name}/.git")).to_not exist + end + + it "doesn't create a .gitignore file" do + expect(bundled_app("#{gem_name}/.gitignore")).to_not exist + end + end + + it "generates a valid gemspec" do + in_app_root + bundle! "gem newgem --bin" + + process_file(bundled_app("newgem", "newgem.gemspec")) do |line| + # Simulate replacing TODOs with real values + case line + when /spec\.metadata\["(?:allowed_push_host|homepage_uri|source_code_uri|changelog_uri)"\]/, /spec\.homepage/ + line.gsub(/\=.*$/, "= 'http://example.org'") + when /spec\.summary/ + line.gsub(/\=.*$/, "= %q{A short summary of my new gem.}") + when /spec\.description/ + line.gsub(/\=.*$/, "= %q{A longer description of my new gem.}") + # Remove exception that prevents public pushes on older RubyGems versions + when /raise "RubyGems 2.0 or newer/ + line.gsub(/.*/, "") if Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.0") + else + line + end + end + + Dir.chdir(bundled_app("newgem")) do + gems = ["rake-10.0.2", :bundler] + # for Ruby core repository, Ruby 2.6+ has bundler as standard library. + gems.delete(:bundler) if ruby_core? + system_gems gems, :path => :bundle_path + bundle! "exec rake build" + end + + expect(last_command.stdboth).not_to include("ERROR") + end + + context "gem naming with relative paths" do + before do + reset! + in_app_root + end + + it "resolves ." do + create_temporary_dir("tmp") + + bundle "gem ." + + expect(bundled_app("tmp/lib/tmp.rb")).to exist + end + + it "resolves .." do + create_temporary_dir("temp/empty_dir") + + bundle "gem .." + + expect(bundled_app("temp/lib/temp.rb")).to exist + end + + it "resolves relative directory" do + create_temporary_dir("tmp/empty/tmp") + + bundle "gem ../../empty" + + expect(bundled_app("tmp/empty/lib/empty.rb")).to exist + end + + def create_temporary_dir(dir) + FileUtils.mkdir_p(dir) + Dir.chdir(dir) + end + end + + context "gem naming with underscore" do + let(:gem_name) { "test_gem" } + + before do + execute_bundle_gem(gem_name) + end + + let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) } + + it "generates a gem skeleton" do + expect(bundled_app("test_gem/test_gem.gemspec")).to exist + expect(bundled_app("test_gem/Gemfile")).to exist + expect(bundled_app("test_gem/Rakefile")).to exist + expect(bundled_app("test_gem/lib/test_gem.rb")).to exist + expect(bundled_app("test_gem/lib/test_gem/version.rb")).to exist + expect(bundled_app("test_gem/.gitignore")).to exist + + expect(bundled_app("test_gem/bin/setup")).to exist + expect(bundled_app("test_gem/bin/console")).to exist + expect(bundled_app("test_gem/bin/setup")).to be_executable + expect(bundled_app("test_gem/bin/console")).to be_executable + end + + it "starts with version 0.1.0" do + expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/VERSION = "0.1.0"/) + end + + it "does not nest constants" do + expect(bundled_app("test_gem/lib/test_gem/version.rb").read).to match(/module TestGem/) + expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(/module TestGem/) + end + + it_should_behave_like "git config is present" + + context "git config user.{name,email} is not set" do + before do + `git config --unset user.name` + `git config --unset user.email` + reset! + in_app_root + bundle "gem #{gem_name}" + remove_push_guard(gem_name) + end + + it_should_behave_like "git config is absent" + end + + it "sets gemspec metadata['allowed_push_host']", :rubygems => "2.0" do + expect(generated_gem.gemspec.metadata["allowed_push_host"]). + to match(/mygemserver\.com/) + end + + it "requires the version file" do + expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(%r{require "test_gem/version"}) + end + + it "creates a base error class" do + expect(bundled_app("test_gem/lib/test_gem.rb").read).to match(/class Error < StandardError; end$/) + end + + it "runs rake without problems" do + system_gems ["rake-10.0.2"] + + rakefile = strip_whitespace <<-RAKEFILE + task :default do + puts 'SUCCESS' + end + RAKEFILE + File.open(bundled_app("test_gem/Rakefile"), "w") do |file| + file.puts rakefile + end + + Dir.chdir(bundled_app(gem_name)) do + sys_exec(rake) + expect(out).to include("SUCCESS") + end + end + + context "--exe parameter set" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --exe" + end + + it "builds exe skeleton" do + expect(bundled_app("test_gem/exe/test_gem")).to exist + end + + it "requires 'test-gem'" do + expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/) + end + end + + context "--bin parameter set" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --bin" + end + + it "builds exe skeleton" do + expect(bundled_app("test_gem/exe/test_gem")).to exist + end + + it "requires 'test-gem'" do + expect(bundled_app("test_gem/exe/test_gem").read).to match(/require "test_gem"/) + end + end + + context "no --test parameter" do + before do + reset! + in_app_root + bundle "gem #{gem_name}" + end + + it "doesn't create any spec/test file" do + expect(bundled_app("test_gem/.rspec")).to_not exist + expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to_not exist + expect(bundled_app("test_gem/spec/spec_helper.rb")).to_not exist + expect(bundled_app("test_gem/test/test_test_gem.rb")).to_not exist + expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist + end + end + + context "--test parameter set to rspec" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test=rspec" + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/.rspec")).to exist + expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist + expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist + end + + it "depends on a specific version of rspec", :rubygems => ">= 1.8.1" do + remove_push_guard(gem_name) + rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "rspec" } + expect(rspec_dep).to be_specific + end + + it "requires 'test-gem'" do + expect(bundled_app("test_gem/spec/spec_helper.rb").read).to include(%(require "test_gem")) + end + + it "creates a default test which fails" do + expect(bundled_app("test_gem/spec/test_gem_spec.rb").read).to include("expect(false).to eq(true)") + end + end + + context "gem.test setting set to rspec" do + before do + reset! + in_app_root + bundle "config gem.test rspec" + bundle "gem #{gem_name}" + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/.rspec")).to exist + expect(bundled_app("test_gem/spec/test_gem_spec.rb")).to exist + expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist + end + end + + context "gem.test setting set to rspec and --test is set to minitest" do + before do + reset! + in_app_root + bundle "config gem.test rspec" + bundle "gem #{gem_name} --test=minitest" + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist + expect(bundled_app("test_gem/test/test_helper.rb")).to exist + end + end + + context "--test parameter set to minitest" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test=minitest" + end + + it "depends on a specific version of minitest", :rubygems => ">= 1.8.1" do + remove_push_guard(gem_name) + rspec_dep = generated_gem.gemspec.development_dependencies.find {|d| d.name == "minitest" } + expect(rspec_dep).to be_specific + end + + it "builds spec skeleton" do + expect(bundled_app("test_gem/test/test_gem_test.rb")).to exist + expect(bundled_app("test_gem/test/test_helper.rb")).to exist + end + + it "requires 'test-gem'" do + expect(bundled_app("test_gem/test/test_helper.rb").read).to include(%(require "test_gem")) + end + + it "requires 'minitest_helper'" do + expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include(%(require "test_helper")) + end + + it "creates a default test which fails" do + expect(bundled_app("test_gem/test/test_gem_test.rb").read).to include("assert false") + end + end + + context "gem.test setting set to minitest" do + before do + reset! + in_app_root + bundle "config gem.test minitest" + bundle "gem #{gem_name}" + end + + it "creates a default rake task to run the test suite" do + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + require "rake/testtask" + + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + end + + task :default => :test + RAKEFILE + + expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile) + end + end + + context "--test with no arguments" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test" + end + + it "defaults to rspec" do + expect(bundled_app("test_gem/spec/spec_helper.rb")).to exist + expect(bundled_app("test_gem/test/minitest_helper.rb")).to_not exist + end + + it "creates a .travis.yml file to test the library against the current Ruby version on Travis CI" do + expect(bundled_app("test_gem/.travis.yml").read).to match(/- #{RUBY_VERSION}/) + end + end + + context "--edit option" do + it "opens the generated gemspec in the user's text editor" do + reset! + in_app_root + output = bundle "gem #{gem_name} --edit=echo" + gemspec_path = File.join(Dir.pwd, gem_name, "#{gem_name}.gemspec") + expect(output).to include("echo \"#{gemspec_path}\"") + end + end + end + + context "testing --mit and --coc options against bundle config settings" do + let(:gem_name) { "test-gem" } + + context "with mit option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__MIT" => "true", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" + end + after { reset! } + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end + + context "with mit option in bundle config settings set to false" do + it_behaves_like "--mit flag" + it_behaves_like "--no-mit flag" + end + + context "with coc option in bundle config settings set to true" do + before do + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "true" + end + after { reset! } + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" + end + + context "with coc option in bundle config settings set to false" do + it_behaves_like "--coc flag" + it_behaves_like "--no-coc flag" + end + end + + context "gem naming with dashed" do + let(:gem_name) { "test-gem" } + + before do + execute_bundle_gem(gem_name) + end + + let(:generated_gem) { Bundler::GemHelper.new(bundled_app(gem_name).to_s) } + + it "generates a gem skeleton" do + expect(bundled_app("test-gem/test-gem.gemspec")).to exist + expect(bundled_app("test-gem/Gemfile")).to exist + expect(bundled_app("test-gem/Rakefile")).to exist + expect(bundled_app("test-gem/lib/test/gem.rb")).to exist + expect(bundled_app("test-gem/lib/test/gem/version.rb")).to exist + end + + it "starts with version 0.1.0" do + expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/VERSION = "0.1.0"/) + end + + it "nests constants so they work" do + expect(bundled_app("test-gem/lib/test/gem/version.rb").read).to match(/module Test\n module Gem/) + expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(/module Test\n module Gem/) + end + + it_should_behave_like "git config is present" + + context "git config user.{name,email} is not set" do + before do + `git config --unset user.name` + `git config --unset user.email` + reset! + in_app_root + bundle "gem #{gem_name}" + remove_push_guard(gem_name) + end + + it_should_behave_like "git config is absent" + end + + it "requires the version file" do + expect(bundled_app("test-gem/lib/test/gem.rb").read).to match(%r{require "test/gem/version"}) + end + + it "runs rake without problems" do + system_gems ["rake-10.0.2"] + + rakefile = strip_whitespace <<-RAKEFILE + task :default do + puts 'SUCCESS' + end + RAKEFILE + File.open(bundled_app("test-gem/Rakefile"), "w") do |file| + file.puts rakefile + end + + Dir.chdir(bundled_app(gem_name)) do + sys_exec(rake) + expect(out).to include("SUCCESS") + end + end + + context "--bin parameter set" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --bin" + end + + it "builds bin skeleton" do + expect(bundled_app("test-gem/exe/test-gem")).to exist + end + + it "requires 'test/gem'" do + expect(bundled_app("test-gem/exe/test-gem").read).to match(%r{require "test/gem"}) + end + end + + context "no --test parameter" do + before do + reset! + in_app_root + bundle "gem #{gem_name}" + end + + it "doesn't create any spec/test file" do + expect(bundled_app("test-gem/.rspec")).to_not exist + expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to_not exist + expect(bundled_app("test-gem/spec/spec_helper.rb")).to_not exist + expect(bundled_app("test-gem/test/test_test/gem.rb")).to_not exist + expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist + end + end + + context "--test parameter set to rspec" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test=rspec" + end + + it "builds spec skeleton" do + expect(bundled_app("test-gem/.rspec")).to exist + expect(bundled_app("test-gem/spec/test/gem_spec.rb")).to exist + expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist + end + + it "requires 'test/gem'" do + expect(bundled_app("test-gem/spec/spec_helper.rb").read).to include(%(require "test/gem")) + end + + it "creates a default test which fails" do + expect(bundled_app("test-gem/spec/test/gem_spec.rb").read).to include("expect(false).to eq(true)") + end + + it "creates a default rake task to run the specs" do + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + require "rspec/core/rake_task" + + RSpec::Core::RakeTask.new(:spec) + + task :default => :spec + RAKEFILE + + expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile) + end + end + + context "--test parameter set to minitest" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test=minitest" + end + + it "builds spec skeleton" do + expect(bundled_app("test-gem/test/test/gem_test.rb")).to exist + expect(bundled_app("test-gem/test/test_helper.rb")).to exist + end + + it "requires 'test/gem'" do + expect(bundled_app("test-gem/test/test_helper.rb").read).to match(%r{require "test/gem"}) + end + + it "requires 'test_helper'" do + expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/require "test_helper"/) + end + + it "creates a default test which fails" do + expect(bundled_app("test-gem/test/test/gem_test.rb").read).to match(/assert false/) + end + + it "creates a default rake task to run the test suite" do + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + require "rake/testtask" + + Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/*_test.rb"] + end + + task :default => :test + RAKEFILE + + expect(bundled_app("test-gem/Rakefile").read).to eq(rakefile) + end + end + + context "--test with no arguments" do + before do + reset! + in_app_root + bundle "gem #{gem_name} --test" + end + + it "defaults to rspec" do + expect(bundled_app("test-gem/spec/spec_helper.rb")).to exist + expect(bundled_app("test-gem/test/minitest_helper.rb")).to_not exist + end + end + + context "--ext parameter set" do + before do + reset! + in_app_root + bundle "gem test_gem --ext" + end + + it "builds ext skeleton" do + expect(bundled_app("test_gem/ext/test_gem/extconf.rb")).to exist + expect(bundled_app("test_gem/ext/test_gem/test_gem.h")).to exist + expect(bundled_app("test_gem/ext/test_gem/test_gem.c")).to exist + end + + it "includes rake-compiler" do + expect(bundled_app("test_gem/test_gem.gemspec").read).to include('spec.add_development_dependency "rake-compiler"') + end + + it "depends on compile task for build" do + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + require "rake/extensiontask" + + task :build => :compile + + Rake::ExtensionTask.new("test_gem") do |ext| + ext.lib_dir = "lib/test_gem" + end + + task :default => [:clobber, :compile, :spec] + RAKEFILE + + expect(bundled_app("test_gem/Rakefile").read).to eq(rakefile) + end + end + end + + describe "uncommon gem names" do + it "can deal with two dashes" do + bundle "gem a--a" + Bundler.clear_gemspec_cache + + expect(bundled_app("a--a/a--a.gemspec")).to exist + end + + it "fails gracefully with a ." do + bundle "gem foo.gemspec" + expect(last_command.bundler_err).to end_with("Invalid gem name foo.gemspec -- `Foo.gemspec` is an invalid constant name") + end + + it "fails gracefully with a ^" do + bundle "gem ^" + expect(last_command.bundler_err).to end_with("Invalid gem name ^ -- `^` is an invalid constant name") + end + + it "fails gracefully with a space" do + bundle "gem 'foo bar'" + expect(last_command.bundler_err).to end_with("Invalid gem name foo bar -- `Foo bar` is an invalid constant name") + end + + it "fails gracefully when multiple names are passed" do + bundle "gem foo bar baz" + expect(last_command.bundler_err).to eq(<<-E.strip) +ERROR: "bundle gem" was called with arguments ["foo", "bar", "baz"] +Usage: "bundle gem NAME [OPTIONS]" + E + end + end + + describe "#ensure_safe_gem_name" do + before do + bundle "gem #{subject}" + end + after do + Bundler.clear_gemspec_cache + end + + context "with an existing const name" do + subject { "gem" } + it { expect(out).to include("Invalid gem name #{subject}") } + end + + context "with an existing hyphenated const name" do + subject { "gem-specification" } + it { expect(out).to include("Invalid gem name #{subject}") } + end + + context "starting with an existing const name" do + subject { "gem-somenewconstantname" } + it { expect(out).not_to include("Invalid gem name #{subject}") } + end + + context "ending with an existing const name" do + subject { "somenewconstantname-gem" } + it { expect(out).not_to include("Invalid gem name #{subject}") } + end + end + + context "on first run" do + before do + in_app_root + end + + it "asks about test framework" do + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__COC" => "false" + + bundle "gem foobar" do |input, _, _| + input.puts "rspec" + end + + expect(bundled_app("foobar/spec/spec_helper.rb")).to exist + rakefile = strip_whitespace <<-RAKEFILE + require "bundler/gem_tasks" + require "rspec/core/rake_task" + + RSpec::Core::RakeTask.new(:spec) + + task :default => :spec + RAKEFILE + + expect(bundled_app("foobar/Rakefile").read).to eq(rakefile) + expect(bundled_app("foobar/foobar.gemspec").read).to include('spec.add_development_dependency "rspec"') + end + + it "asks about MIT license" do + global_config "BUNDLE_GEM__TEST" => "false", "BUNDLE_GEM__COC" => "false" + + bundle :config + + bundle "gem foobar" do |input, _, _| + input.puts "yes" + end + + expect(bundled_app("foobar/LICENSE.txt")).to exist + end + + it "asks about CoC" do + global_config "BUNDLE_GEM__MIT" => "false", "BUNDLE_GEM__TEST" => "false" + + bundle "gem foobar" do |input, _, _| + input.puts "yes" + end + + expect(bundled_app("foobar/CODE_OF_CONDUCT.md")).to exist + end + end + + context "on conflicts with a previously created file" do + it "should fail gracefully" do + in_app_root do + FileUtils.touch("conflict-foobar") + end + bundle "gem conflict-foobar" + expect(last_command.bundler_err).to include("Errno::ENOTDIR") + expect(exitstatus).to eql(32) if exitstatus + end + end + + context "on conflicts with a previously created directory" do + it "should succeed" do + in_app_root do + FileUtils.mkdir_p("conflict-foobar/Gemfile") + end + bundle! "gem conflict-foobar" + expect(last_command.stdout).to include("file_clash conflict-foobar/Gemfile"). + and include "Initializing git repo in #{bundled_app("conflict-foobar")}" + end + end +end diff --git a/spec/bundler/commands/open_spec.rb b/spec/bundler/commands/open_spec.rb new file mode 100644 index 0000000000..5cab846fb5 --- /dev/null +++ b/spec/bundler/commands/open_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +RSpec.describe "bundle open" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + end + + it "opens the gem with BUNDLER_EDITOR as highest priority" do + bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + expect(out).to include("bundler_editor #{default_bundle_path("gems", "rails-2.3.2")}") + end + + it "opens the gem with VISUAL as 2nd highest priority" do + bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "" } + expect(out).to include("visual #{default_bundle_path("gems", "rails-2.3.2")}") + end + + it "opens the gem with EDITOR as 3rd highest priority" do + bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("editor #{default_bundle_path("gems", "rails-2.3.2")}") + end + + it "complains if no EDITOR is set" do + bundle "open rails", :env => { "EDITOR" => "", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to eq("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") + end + + it "complains if gem not in bundle" do + bundle "open missing", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to match(/could not find gem 'missing'/i) + end + + it "does not blow up if the gem to open does not have a Gemfile" do + git = build_git "foo" + ref = git.ref_for("master", 11) + + install_gemfile <<-G + source "file://#{gem_repo1}" + gem 'foo', :git => "#{lib_path("foo-1.0")}" + G + + bundle "open foo", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to match("editor #{default_bundle_path.join("bundler/gems/foo-1.0-#{ref}")}") + end + + it "suggests alternatives for similar-sounding gems" do + bundle "open Rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to match(/did you mean rails\?/i) + end + + it "opens the gem with short words" do + bundle "open rec", :env => { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + + expect(out).to include("bundler_editor #{default_bundle_path("gems", "activerecord-2.3.2")}") + end + + it "select the gem from many match gems" do + env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle "open active", :env => env do |input, _, _| + input.puts "2" + end + + expect(out).to match(/bundler_editor #{default_bundle_path('gems', 'activerecord-2.3.2')}\z/) + end + + it "allows selecting exit from many match gems" do + env = { "EDITOR" => "echo editor", "VISUAL" => "echo visual", "BUNDLER_EDITOR" => "echo bundler_editor" } + bundle! "open active", :env => env do |input, _, _| + input.puts "0" + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + gem "foo" + G + + bundle "config auto_install 1" + bundle "open rails", :env => { "EDITOR" => "echo editor", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).to include("Installing foo 1.0") + end + + it "opens the editor with a clean env" do + bundle "open", :env => { "EDITOR" => "sh -c 'env'", "VISUAL" => "", "BUNDLER_EDITOR" => "" } + expect(out).not_to include("BUNDLE_GEMFILE=") + end +end diff --git a/spec/bundler/commands/outdated_spec.rb b/spec/bundler/commands/outdated_spec.rb new file mode 100644 index 0000000000..fc1f1772e7 --- /dev/null +++ b/spec/bundler/commands/outdated_spec.rb @@ -0,0 +1,782 @@ +# frozen_string_literal: true + +RSpec.describe "bundle outdated" do + before :each do + build_repo2 do + build_git "foo", :path => lib_path("foo") + build_git "zebra", :path => lib_path("zebra") + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "zebra", :git => "#{lib_path("zebra")}" + gem "foo", :git => "#{lib_path("foo")}" + gem "activesupport", "2.3.5" + gem "weakling", "~> 0.0.1" + gem "duradura", '7.0' + gem "terranova", '8' + G + end + + describe "with no arguments" do + it "returns a sorted list of outdated gems" do + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "weakling", "0.2" + update_git "foo", :path => lib_path("foo") + update_git "zebra", :path => lib_path("zebra") + end + + bundle "outdated" + + expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)") + expect(out).to include("weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)") + expect(out).to include("foo (newest 1.0") + + # Gem names are one per-line, between "*" and their parenthesized version. + gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact + expect(gem_list).to eq(gem_list.sort) + end + + it "returns non zero exit status if outdated gems present" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + bundle "outdated" + + expect(exitstatus).to_not be_zero if exitstatus + end + + it "returns success exit status if no outdated gems present" do + bundle "outdated" + + expect(exitstatus).to be_zero if exitstatus + end + + it "adds gem group to dependency output when repo is updated" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + group :development, :test do + gem 'activesupport', '2.3.5' + end + G + + update_repo2 { build_gem "activesupport", "3.0" } + + bundle "outdated --verbose" + expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5) in groups \"development, test\"") + end + end + + describe "with --group option" do + def test_group_option(group = nil, gems_list_size = 1) + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem "duradura", '7.0' + gem 'activesupport', '2.3.5' + end + G + + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "terranova", "9" + build_gem "duradura", "8.0" + end + + bundle "outdated --group #{group}" + + # Gem names are one per-line, between "*" and their parenthesized version. + gem_list = out.split("\n").map {|g| g[/\* (.*) \(/, 1] }.compact + expect(gem_list).to eq(gem_list.sort) + expect(gem_list.size).to eq gems_list_size + end + + it "not outdated gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + bundle "outdated --group" + expect(out).to include("Bundle up to date!") + end + + it "returns a sorted list of outdated gems from one group => 'default'" do + test_group_option("default") + + expect(out).to include("===== Group default =====") + expect(out).to include("terranova (") + + expect(out).not_to include("===== Group development, test =====") + expect(out).not_to include("activesupport") + expect(out).not_to include("duradura") + end + + it "returns a sorted list of outdated gems from one group => 'development'" do + test_group_option("development", 2) + + expect(out).not_to include("===== Group default =====") + expect(out).not_to include("terranova (") + + expect(out).to include("===== Group development, test =====") + expect(out).to include("activesupport") + expect(out).to include("duradura") + end + end + + describe "with --groups option" do + it "not outdated gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + bundle "outdated --groups" + expect(out).to include("Bundle up to date!") + end + + it "returns a sorted list of outdated gems by groups" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "weakling", "~> 0.0.1" + gem "terranova", '8' + group :development, :test do + gem 'activesupport', '2.3.5' + gem "duradura", '7.0' + end + G + + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "terranova", "9" + build_gem "duradura", "8.0" + end + + bundle "outdated --groups" + expect(out).to include("===== Group default =====") + expect(out).to include("terranova (newest 9, installed 8, requested = 8)") + expect(out).to include("===== Group development, test =====") + expect(out).to include("activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)") + expect(out).to include("duradura (newest 8.0, installed 7.0, requested = 7.0)") + + expect(out).not_to include("weakling (") + + # TODO: check gems order inside the group + end + end + + describe "with --local option" do + it "uses local cache to return a list of outdated gems" do + update_repo2 do + build_gem "activesupport", "2.3.4" + end + + bundle! "config clean false" + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "2.3.4" + G + + bundle "outdated --local" + + expect(out).to include("activesupport (newest 2.3.5, installed 2.3.4, requested = 2.3.4)") + end + + it "doesn't hit repo2" do + FileUtils.rm_rf(gem_repo2) + + bundle "outdated --local" + expect(out).not_to match(/Fetching (gem|version|dependency) metadata from/) + end + end + + shared_examples_for "a minimal output is desired" do + context "and gems are outdated" do + before do + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "weakling", "0.2" + end + end + + it "outputs a sorted list of outdated gems with a more minimal format" do + minimal_output = "activesupport (newest 3.0, installed 2.3.5, requested = 2.3.5)\n" \ + "weakling (newest 0.2, installed 0.0.3, requested ~> 0.0.1)" + subject + expect(out).to eq(minimal_output) + end + end + + context "and no gems are outdated" do + it "has empty output" do + subject + expect(out).to eq("") + end + end + end + + describe "with --parseable option" do + subject { bundle "outdated --parseable" } + + it_behaves_like "a minimal output is desired" + end + + describe "with aliased --porcelain option" do + subject { bundle "outdated --porcelain" } + + it_behaves_like "a minimal output is desired" + end + + describe "with specified gems" do + it "returns list of outdated gems" do + update_repo2 do + build_gem "activesupport", "3.0" + update_git "foo", :path => lib_path("foo") + end + + bundle "outdated foo" + expect(out).not_to include("activesupport (newest") + expect(out).to include("foo (newest 1.0") + end + end + + describe "pre-release gems" do + context "without the --pre option" do + it "ignores pre-release versions" do + update_repo2 do + build_gem "activesupport", "3.0.0.beta" + end + + bundle "outdated" + expect(out).not_to include("activesupport (3.0.0.beta > 2.3.5)") + end + end + + context "with the --pre option" do + it "includes pre-release versions" do + update_repo2 do + build_gem "activesupport", "3.0.0.beta" + end + + bundle "outdated --pre" + expect(out).to include("activesupport (newest 3.0.0.beta, installed 2.3.5, requested = 2.3.5)") + end + end + + context "when current gem is a pre-release" do + it "includes the gem" do + update_repo2 do + build_gem "activesupport", "3.0.0.beta.1" + build_gem "activesupport", "3.0.0.beta.2" + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "3.0.0.beta.1" + G + + bundle "outdated" + expect(out).to include("(newest 3.0.0.beta.2, installed 3.0.0.beta.1, requested = 3.0.0.beta.1)") + end + end + end + + describe "with --strict option" do + it "only reports gems that have a newer version that matches the specified dependency version requirements" do + update_repo2 do + build_gem "activesupport", "3.0" + build_gem "weakling", "0.0.5" + end + + bundle "outdated --strict" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 0.0.5, installed 0.0.3, requested ~> 0.0.1)") + end + + it "only reports gem dependencies when they can actually be updated" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack_middleware", "1.0" + G + + bundle "outdated --strict" + + expect(out).to_not include("rack (1.2") + end + + describe "and filter options" do + it "only reports gems that match requirement and patch filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w[2.4.0 3.0.0] + build_gem "weakling", "0.0.5" + end + + bundle "outdated --strict --filter-patch" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 0.0.5, installed 0.0.3") + end + + it "only reports gems that match requirement and minor filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w[2.3.9] + build_gem "weakling", "0.1.5" + end + + bundle "outdated --strict --filter-minor" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 0.1.5, installed 0.0.3") + end + + it "only reports gems that match requirement and major filter level" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", "~> 2.3" + gem "weakling", ">= 0.0.1" + G + + update_repo2 do + build_gem "activesupport", %w[2.4.0 2.5.0] + build_gem "weakling", "1.1.5" + end + + bundle "outdated --strict --filter-major" + + expect(out).to_not include("activesupport (newest") + expect(out).to include("(newest 1.1.5, installed 0.0.3") + end + end + end + + describe "with invalid gem name" do + it "returns could not find gem name" do + bundle "outdated invalid_gem_name" + expect(out).to include("Could not find gem 'invalid_gem_name'.") + end + + it "returns non-zero exit code" do + bundle "outdated invalid_gem_name" + expect(exitstatus).to_not be_zero if exitstatus + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack", "0.9.1" + gem "foo" + G + + bundle "config auto_install 1" + bundle :outdated + expect(out).to include("Installing foo 1.0") + end + + context "after bundle install --deployment", :bundler => "< 2" do + before do + install_gemfile <<-G, forgotten_command_line_options(:deployment => true) + source "file://#{gem_repo2}" + + gem "rack" + gem "foo" + G + end + + it "outputs a helpful message about being in deployment mode" do + update_repo2 { build_gem "activesupport", "3.0" } + + bundle "outdated" + expect(last_command).to be_failure + expect(out).to include("You are trying to check outdated gems in deployment mode.") + expect(out).to include("Run `bundle outdated` elsewhere.") + expect(out).to include("If this is a development machine, remove the ") + expect(out).to include("Gemfile freeze\nby running `bundle install --no-deployment`.") + end + end + + context "after bundle config deployment true" do + before do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "rack" + gem "foo" + G + bundle! "config deployment true" + end + + it "outputs a helpful message about being in deployment mode" do + update_repo2 { build_gem "activesupport", "3.0" } + + bundle "outdated" + expect(last_command).to be_failure + expect(out).to include("You are trying to check outdated gems in deployment mode.") + expect(out).to include("Run `bundle outdated` elsewhere.") + expect(out).to include("If this is a development machine, remove the ") + expect(out).to include("Gemfile freeze\nby running `bundle config --delete deployment`.") + end + end + + context "update available for a gem on a different platform" do + before do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "laduradura", '= 5.15.2' + G + end + + it "reports that no updates are available" do + bundle "outdated" + expect(out).to include("Bundle up to date!") + end + end + + context "update available for a gem on the same platform while multiple platforms used for gem" do + it "reports that updates are available if the Ruby platform is used" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] + G + + bundle "outdated" + expect(out).to include("Bundle up to date!") + end + + it "reports that updates are available if the JRuby platform is used" do + simulate_ruby_engine "jruby", "1.6.7" do + simulate_platform "jruby" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "laduradura", '= 5.15.2', :platforms => [:ruby, :jruby] + G + + bundle "outdated" + expect(out).to include("Outdated gems included in the bundle:") + expect(out).to include("laduradura (newest 5.15.3, installed 5.15.2, requested = 5.15.2)") + end + end + end + end + + shared_examples_for "version update is detected" do + it "reports that a gem has a newer version" do + subject + expect(out).to include("Outdated gems included in the bundle:") + expect(out).to include("activesupport (newest") + expect(out).to_not include("ERROR REPORT TEMPLATE") + end + end + + shared_examples_for "major version updates are detected" do + before do + update_repo2 do + build_gem "activesupport", "3.3.5" + build_gem "weakling", "0.8.0" + end + end + + it_behaves_like "version update is detected" + end + + context "when on a new machine" do + before do + simulate_new_machine + + update_git "foo", :path => lib_path("foo") + update_repo2 do + build_gem "activesupport", "3.3.5" + build_gem "weakling", "0.8.0" + end + end + + subject { bundle "outdated" } + it_behaves_like "version update is detected" + end + + shared_examples_for "minor version updates are detected" do + before do + update_repo2 do + build_gem "activesupport", "2.7.5" + build_gem "weakling", "2.0.1" + end + end + + it_behaves_like "version update is detected" + end + + shared_examples_for "patch version updates are detected" do + before do + update_repo2 do + build_gem "activesupport", "2.3.7" + build_gem "weakling", "0.3.1" + end + end + + it_behaves_like "version update is detected" + end + + shared_examples_for "no version updates are detected" do + it "does not detect any version updates" do + subject + expect(out).to include("updates to display.") + expect(out).to_not include("ERROR REPORT TEMPLATE") + expect(out).to_not include("activesupport (newest") + expect(out).to_not include("weakling (newest") + end + end + + shared_examples_for "major version is ignored" do + before do + update_repo2 do + build_gem "activesupport", "3.3.5" + build_gem "weakling", "1.0.1" + end + end + + it_behaves_like "no version updates are detected" + end + + shared_examples_for "minor version is ignored" do + before do + update_repo2 do + build_gem "activesupport", "2.4.5" + build_gem "weakling", "0.3.1" + end + end + + it_behaves_like "no version updates are detected" + end + + shared_examples_for "patch version is ignored" do + before do + update_repo2 do + build_gem "activesupport", "2.3.6" + build_gem "weakling", "0.0.4" + end + end + + it_behaves_like "no version updates are detected" + end + + describe "with --filter-major option" do + subject { bundle "outdated --filter-major" } + + it_behaves_like "major version updates are detected" + it_behaves_like "minor version is ignored" + it_behaves_like "patch version is ignored" + end + + describe "with --filter-minor option" do + subject { bundle "outdated --filter-minor" } + + it_behaves_like "minor version updates are detected" + it_behaves_like "major version is ignored" + it_behaves_like "patch version is ignored" + end + + describe "with --filter-patch option" do + subject { bundle "outdated --filter-patch" } + + it_behaves_like "patch version updates are detected" + it_behaves_like "major version is ignored" + it_behaves_like "minor version is ignored" + end + + describe "with --filter-minor --filter-patch options" do + subject { bundle "outdated --filter-minor --filter-patch" } + + it_behaves_like "minor version updates are detected" + it_behaves_like "patch version updates are detected" + it_behaves_like "major version is ignored" + end + + describe "with --filter-major --filter-minor options" do + subject { bundle "outdated --filter-major --filter-minor" } + + it_behaves_like "major version updates are detected" + it_behaves_like "minor version updates are detected" + it_behaves_like "patch version is ignored" + end + + describe "with --filter-major --filter-patch options" do + subject { bundle "outdated --filter-major --filter-patch" } + + it_behaves_like "major version updates are detected" + it_behaves_like "patch version updates are detected" + it_behaves_like "minor version is ignored" + end + + describe "with --filter-major --filter-minor --filter-patch options" do + subject { bundle "outdated --filter-major --filter-minor --filter-patch" } + + it_behaves_like "major version updates are detected" + it_behaves_like "minor version updates are detected" + it_behaves_like "patch version updates are detected" + end + + context "conservative updates" do + context "without update-strict" do + before do + build_repo4 do + build_gem "patch", %w[1.0.0 1.0.1] + build_gem "minor", %w[1.0.0 1.0.1 1.1.0] + build_gem "major", %w[1.0.0 1.0.1 1.1.0 2.0.0] + end + + # establish a lockfile set to 1.0.0 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'patch', '1.0.0' + gem 'minor', '1.0.0' + gem 'major', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'patch' + gem 'minor' + gem 'major' + G + end + + it "shows nothing when patching and filtering to minor" do + bundle "outdated --patch --filter-minor" + + expect(out).to include("No minor updates to display.") + expect(out).not_to include("patch (newest") + expect(out).not_to include("minor (newest") + expect(out).not_to include("major (newest") + end + + it "shows all gems when patching and filtering to patch" do + bundle "outdated --patch --filter-patch" + + expect(out).to include("patch (newest 1.0.1") + expect(out).to include("minor (newest 1.0.1") + expect(out).to include("major (newest 1.0.1") + end + + it "shows minor and major when updating to minor and filtering to patch and minor" do + bundle "outdated --minor --filter-minor" + + expect(out).not_to include("patch (newest") + expect(out).to include("minor (newest 1.1.0") + expect(out).to include("major (newest 1.1.0") + end + + it "shows minor when updating to major and filtering to minor with parseable" do + bundle "outdated --major --filter-minor --parseable" + + expect(out).not_to include("patch (newest") + expect(out).to include("minor (newest") + expect(out).not_to include("major (newest") + end + end + + context "with update-strict" do + before do + build_repo4 do + build_gem "foo", %w[1.4.3 1.4.4] do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w[1.4.5 1.5.0] do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w[1.5.1] do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0] + build_gem "qux", %w[1.0.0 1.1.0 2.0.0] + end + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + it "shows gems with update-strict updating to patch and filtering to patch" do + bundle "outdated --patch --update-strict --filter-patch" + + expect(out).to include("foo (newest 1.4.4") + expect(out).to include("bar (newest 2.0.5") + expect(out).not_to include("qux (newest") + end + end + end + + describe "with --only-explicit" do + it "does not report outdated dependent gems" do + build_repo4 do + build_gem "weakling", %w[0.2 0.3] do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "bar", %w[2.1 2.2] + end + + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'weakling', '0.2' + gem 'bar', '2.1' + G + + gemfile <<-G + source "file://#{gem_repo4}" + gem 'weakling' + G + + bundle "outdated --only-explicit" + + expect(out).to include("weakling (newest 0.3") + expect(out).not_to include("bar (newest 2.2") + end + end +end diff --git a/spec/bundler/commands/package_spec.rb b/spec/bundler/commands/package_spec.rb new file mode 100644 index 0000000000..6351909bc7 --- /dev/null +++ b/spec/bundler/commands/package_spec.rb @@ -0,0 +1,306 @@ +# frozen_string_literal: true + +RSpec.describe "bundle package" do + context "with --gemfile" do + it "finds the gemfile" do + gemfile bundled_app("NotGemfile"), <<-G + source "file://#{gem_repo1}" + gem 'rack' + G + + bundle "package --gemfile=NotGemfile" + + ENV["BUNDLE_GEMFILE"] = "NotGemfile" + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + context "with --all" do + context "without a gemspec" do + it "caches all dependencies except bundler itself" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + gem 'bundler' + D + + bundle :package, forgotten_command_line_options([:all, :cache_all] => true) + + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist + end + end + + context "with a gemspec" do + context "that has the same name as the gem" do + before do + File.open(bundled_app("mygem.gemspec"), "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = "mygem" + s.version = "0.1.1" + s.summary = "" + s.authors = ["gem author"] + s.add_development_dependency "nokogiri", "=1.4.2" + end + G + end + end + + it "caches all dependencies except bundler and the gemspec specified gem" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + gemspec + D + + bundle! :package, forgotten_command_line_options([:all, :cache_all] => true) + + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist + expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist + expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist + end + end + + context "that has a different name as the gem" do + before do + File.open(bundled_app("mygem_diffname.gemspec"), "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = "mygem" + s.version = "0.1.1" + s.summary = "" + s.authors = ["gem author"] + s.add_development_dependency "nokogiri", "=1.4.2" + end + G + end + end + + it "caches all dependencies except bundler and the gemspec specified gem" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + gemspec + D + + bundle! :package, forgotten_command_line_options([:all, :cache_all] => true) + + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist + expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist + expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist + end + end + end + + context "with multiple gemspecs" do + before do + File.open(bundled_app("mygem.gemspec"), "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = "mygem" + s.version = "0.1.1" + s.summary = "" + s.authors = ["gem author"] + s.add_development_dependency "nokogiri", "=1.4.2" + end + G + end + File.open(bundled_app("mygem_client.gemspec"), "w") do |f| + f.write <<-G + Gem::Specification.new do |s| + s.name = "mygem_test" + s.version = "0.1.1" + s.summary = "" + s.authors = ["gem author"] + s.add_development_dependency "weakling", "=0.0.3" + end + G + end + end + + it "caches all dependencies except bundler and the gemspec specified gems" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + gemspec :name => 'mygem' + gemspec :name => 'mygem_test' + D + + bundle! :package, forgotten_command_line_options([:all, :cache_all] => true) + + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + expect(bundled_app("vendor/cache/nokogiri-1.4.2.gem")).to exist + expect(bundled_app("vendor/cache/weakling-0.0.3.gem")).to exist + expect(bundled_app("vendor/cache/mygem-0.1.1.gem")).to_not exist + expect(bundled_app("vendor/cache/mygem_test-0.1.1.gem")).to_not exist + expect(bundled_app("vendor/cache/bundler-0.9.gem")).to_not exist + end + end + end + + context "with --path", :bundler => "< 2" do + it "sets root directory for gems" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + D + + bundle! :package, forgotten_command_line_options(:path => bundled_app("test")) + + expect(the_bundle).to include_gems "rack 1.0.0" + expect(bundled_app("test/vendor/cache/")).to exist + end + end + + context "with --no-install" do + it "puts the gems in vendor/cache but does not install them" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + D + + bundle! "package --no-install" + + expect(the_bundle).not_to include_gems "rack 1.0.0" + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + end + + it "does not prevent installing gems with bundle install" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack' + D + + bundle! "package --no-install" + bundle! "install" + + expect(the_bundle).to include_gems "rack 1.0.0" + end + end + + context "with --all-platforms" do + it "puts the gems in vendor/cache even for other rubies", :ruby => "2.1" do + gemfile <<-D + source "file://#{gem_repo1}" + gem 'rack', :platforms => :ruby_19 + D + + bundle "package --all-platforms" + expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist + end + end + + context "with --frozen" do + before do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle "install" + end + + subject { bundle :package, forgotten_command_line_options(:frozen => true) } + + it "tries to install with frozen" do + bundle! "config deployment true" + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + subject + expect(exitstatus).to eq(16) if exitstatus + expect(out).to include("deployment mode") + expect(out).to include("You have added to the Gemfile") + expect(out).to include("* rack-obama") + bundle "env" + expect(out).to include("frozen").or include("deployment") + end + end +end + +RSpec.describe "bundle install with gem sources" do + describe "when cached and locked" do + it "does not hit the remote at all" do + build_repo2 + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack" + G + + bundle :pack + simulate_new_machine + FileUtils.rm_rf gem_repo2 + + bundle "install --local" + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "does not hit the remote at all" do + build_repo2 + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rack" + G + + bundle! :pack + simulate_new_machine + FileUtils.rm_rf gem_repo2 + + bundle! :install, forgotten_command_line_options(:deployment => true, :path => "vendor/bundle") + expect(the_bundle).to include_gems "rack 1.0.0" + end + + it "does not reinstall already-installed gems" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundle :pack + + build_gem "rack", "1.0.0", :path => bundled_app("vendor/cache") do |s| + s.write "lib/rack.rb", "raise 'omg'" + end + + bundle :install + expect(err).to lack_errors + expect(the_bundle).to include_gems "rack 1.0" + end + + it "ignores cached gems for the wrong platform" do + simulate_platform "java" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + bundle :pack + end + + simulate_new_machine + + simulate_platform "ruby" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "platform_specific" + G + run "require 'platform_specific' ; puts PLATFORM_SPECIFIC" + expect(out).to eq("1.0.0 RUBY") + end + end + + it "does not update the cache if --no-cache is passed" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + G + bundled_app("vendor/cache").mkpath + expect(bundled_app("vendor/cache").children).to be_empty + + bundle "install --no-cache" + expect(bundled_app("vendor/cache").children).to be_empty + end + end +end diff --git a/spec/bundler/commands/pristine_spec.rb b/spec/bundler/commands/pristine_spec.rb new file mode 100644 index 0000000000..0bfc37560a --- /dev/null +++ b/spec/bundler/commands/pristine_spec.rb @@ -0,0 +1,192 @@ +# frozen_string_literal: true + +require "bundler/vendored_fileutils" + +RSpec.describe "bundle pristine", :ruby_repo do + before :each do + build_lib "baz", :path => bundled_app do |s| + s.version = "1.0.0" + s.add_development_dependency "baz-dev", "=1.0.0" + end + + build_repo2 do + build_gem "weakling" + build_gem "baz-dev", "1.0.0" + build_gem "very_simple_binary", &:add_c_extension + build_git "foo", :path => lib_path("foo") + build_git "git_with_ext", :path => lib_path("git_with_ext"), &:add_c_extension + build_lib "bar", :path => lib_path("bar") + end + + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "weakling" + gem "very_simple_binary" + gem "foo", :git => "#{lib_path("foo")}" + gem "git_with_ext", :git => "#{lib_path("git_with_ext")}" + gem "bar", :path => "#{lib_path("bar")}" + + gemspec + G + end + + context "when sourced from RubyGems" do + it "reverts using cached .gem file" do + spec = Bundler.definition.specs["weakling"].first + changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt") + + FileUtils.touch(changes_txt) + expect(changes_txt).to be_file + + bundle "pristine" + expect(changes_txt).to_not be_file + end + + it "does not delete the bundler gem" do + ENV["BUNDLER_SPEC_KEEP_DEFAULT_BUNDLER_GEM"] = "true" + system_gems :bundler + bundle! "install" + bundle! "pristine", :system_bundler => true + bundle! "-v", :system_bundler => true + + expected = if Bundler::VERSION < "2.0" + "Bundler version" + else + Bundler::VERSION + end + + expect(out).to start_with(expected) + end + end + + context "when sourced from git repo" do + it "reverts by resetting to current revision`" do + spec = Bundler.definition.specs["foo"].first + changed_file = Pathname.new(spec.full_gem_path).join("lib/foo.rb") + diff = "#Pristine spec changes" + + File.open(changed_file, "a") {|f| f.puts diff } + expect(File.read(changed_file)).to include(diff) + + bundle! "pristine" + expect(File.read(changed_file)).to_not include(diff) + end + + it "removes added files" do + spec = Bundler.definition.specs["foo"].first + changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt") + + FileUtils.touch(changes_txt) + expect(changes_txt).to be_file + + bundle! "pristine" + expect(changes_txt).not_to be_file + end + end + + context "when sourced from gemspec" do + it "displays warning and ignores changes when sourced from gemspec" do + spec = Bundler.definition.specs["baz"].first + changed_file = Pathname.new(spec.full_gem_path).join("lib/baz.rb") + diff = "#Pristine spec changes" + + File.open(changed_file, "a") {|f| f.puts diff } + expect(File.read(changed_file)).to include(diff) + + bundle "pristine" + expect(File.read(changed_file)).to include(diff) + expect(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.") + end + + it "reinstall gemspec dependency" do + spec = Bundler.definition.specs["baz-dev"].first + changed_file = Pathname.new(spec.full_gem_path).join("lib/baz-dev.rb") + diff = "#Pristine spec changes" + + File.open(changed_file, "a") {|f| f.puts "#Pristine spec changes" } + expect(File.read(changed_file)).to include(diff) + + bundle "pristine" + expect(File.read(changed_file)).to_not include(diff) + end + end + + context "when sourced from path" do + it "displays warning and ignores changes when sourced from local path" do + spec = Bundler.definition.specs["bar"].first + changes_txt = Pathname.new(spec.full_gem_path).join("lib/changes.txt") + FileUtils.touch(changes_txt) + expect(changes_txt).to be_file + bundle "pristine" + expect(out).to include("Cannot pristine #{spec.name} (#{spec.version}#{spec.git_version}). Gem is sourced from local path.") + expect(changes_txt).to be_file + end + end + + context "when passing a list of gems to pristine" do + it "resets them" do + foo = Bundler.definition.specs["foo"].first + foo_changes_txt = Pathname.new(foo.full_gem_path).join("lib/changes.txt") + FileUtils.touch(foo_changes_txt) + expect(foo_changes_txt).to be_file + + bar = Bundler.definition.specs["bar"].first + bar_changes_txt = Pathname.new(bar.full_gem_path).join("lib/changes.txt") + FileUtils.touch(bar_changes_txt) + expect(bar_changes_txt).to be_file + + weakling = Bundler.definition.specs["weakling"].first + weakling_changes_txt = Pathname.new(weakling.full_gem_path).join("lib/changes.txt") + FileUtils.touch(weakling_changes_txt) + expect(weakling_changes_txt).to be_file + + bundle! "pristine foo bar weakling" + + expect(out).to include("Cannot pristine bar (1.0). Gem is sourced from local path."). + and include("Installing weakling 1.0") + + expect(weakling_changes_txt).not_to be_file + expect(foo_changes_txt).not_to be_file + expect(bar_changes_txt).to be_file + end + + it "raises when one of them is not in the lockfile" do + bundle "pristine abcabcabc" + expect(out).to include("Could not find gem 'abcabcabc'.") + end + end + + context "when a build config exists for one of the gems" do + let(:very_simple_binary) { Bundler.definition.specs["very_simple_binary"].first } + let(:c_ext_dir) { Pathname.new(very_simple_binary.full_gem_path).join("ext") } + let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" } + before { bundle "config build.very_simple_binary -- #{build_opt}" } + + # This just verifies that the generated Makefile from the c_ext gem makes + # use of the build_args from the bundle config + it "applies the config when installing the gem" do + bundle! "pristine" + + makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) + expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + end + end + + context "when a build config exists for a git sourced gem" do + let(:git_with_ext) { Bundler.definition.specs["git_with_ext"].first } + let(:c_ext_dir) { Pathname.new(git_with_ext.full_gem_path).join("ext") } + let(:build_opt) { "--with-ext-lib=#{c_ext_dir}" } + before { bundle "config build.git_with_ext -- #{build_opt}" } + + # This just verifies that the generated Makefile from the c_ext gem makes + # use of the build_args from the bundle config + it "applies the config when installing the gem" do + bundle! "pristine" + + makefile_contents = File.read(c_ext_dir.join("Makefile").to_s) + expect(makefile_contents).to match(/libpath =.*#{c_ext_dir}/) + expect(makefile_contents).to match(/LIBPATH =.*-L#{c_ext_dir}/) + end + end +end diff --git a/spec/bundler/commands/remove_spec.rb b/spec/bundler/commands/remove_spec.rb new file mode 100644 index 0000000000..faeb654b14 --- /dev/null +++ b/spec/bundler/commands/remove_spec.rb @@ -0,0 +1,583 @@ +# frozen_string_literal: true + +RSpec.describe "bundle remove" do + context "when no gems are specified" do + it "throws error" do + gemfile <<-G + source "file://#{gem_repo1}" + G + + bundle "remove" + + expect(out).to include("Please specify gems to remove.") + end + end + + context "when --install flag is specified" do + it "removes gems from .bundle" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + + bundle! "remove rack --install" + + expect(out).to include("rack was removed.") + expect(the_bundle).to_not include_gems "rack" + end + end + + describe "remove single gem from gemfile" do + context "when gem is present in gemfile" do + it "shows success for removed gem" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when gem is not present in gemfile" do + it "shows warning for gem that could not be removed" do + gemfile <<-G + source "file://#{gem_repo1}" + G + + bundle "remove rack" + + expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.") + end + end + end + + describe "remove mutiple gems from gemfile" do + context "when all gems are present in gemfile" do + it "shows success fir all removed gems" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + gem "rails" + G + + bundle! "remove rack rails" + + expect(out).to include("rack was removed.") + expect(out).to include("rails was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when some gems are not present in the gemfile" do + it "shows warning for those not present and success for those that can be removed" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rails" + gem "minitest" + gem "rspec" + G + + bundle "remove rails rack minitest" + + expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + gem "rails" + gem "minitest" + gem "rspec" + G + end + end + end + + context "with inline groups" do + it "removes the specified gem" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack", :group => [:dev] + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + describe "with group blocks" do + context "when single group block with gem to be removed is present" do + it "removes the group block" do + gemfile <<-G + source "file://#{gem_repo1}" + + group :test do + gem "rspec" + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when an empty block is also present" do + it "removes all empty blocks" do + gemfile <<-G + source "file://#{gem_repo1}" + + group :test do + gem "rspec" + end + + group :dev do + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when the gem belongs to mutiple groups" do + it "removes the groups" do + gemfile <<-G + source "file://#{gem_repo1}" + + group :test, :serioustest do + gem "rspec" + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when the gem is present in mutiple groups" do + it "removes all empty blocks" do + gemfile <<-G + source "file://#{gem_repo1}" + + group :one do + gem "rspec" + end + + group :two do + gem "rspec" + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + end + + describe "nested group blocks" do + context "when all the groups will be empty after removal" do + it "removes the empty nested blocks" do + gemfile <<-G + source "file://#{gem_repo1}" + + group :test do + group :serioustest do + gem "rspec" + end + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "when outer group will not be empty after removal" do + it "removes only empty blocks" do + install_gemfile <<-G + source "file://#{gem_repo1}" + + group :test do + gem "rack-test" + + group :serioustest do + gem "rspec" + end + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + group :test do + gem "rack-test" + + end + G + end + end + + context "when inner group will not be empty after removal" do + it "removes only empty blocks" do + install_gemfile <<-G + source "file://#{gem_repo1}" + + group :test do + group :serioustest do + gem "rspec" + gem "rack-test" + end + end + G + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + group :test do + group :serioustest do + gem "rack-test" + end + end + G + end + end + end + + describe "arbitrary gemfile" do + context "when mutiple gems are present in same line" do + it "shows warning for gems not removed" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack"; gem "rails" + G + + bundle "remove rails" + + if Gem::VERSION >= "1.6.0" + expect(out).to include("Gems could not be removed. rack (>= 0) would also have been removed.") + else + expect(out).to include("Gems could not be removed. rack (>= 0, runtime) would also have been removed.") + end + gemfile_should_be <<-G + source "file://#{gem_repo1}" + gem "rack"; gem "rails" + G + end + end + + context "when some gems could not be removed" do + it "shows warning for gems not removed and success for those removed" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem"rack" + gem"rspec" + gem "rails" + gem "minitest" + G + + bundle! "remove rails rack rspec minitest" + + expect(out).to include("rails was removed.") + expect(out).to include("minitest was removed.") + expect(out).to include("rack, rspec could not be removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + gem"rack" + gem"rspec" + G + end + end + end + + context "with sources" do + before do + build_repo gem_repo3 do + build_gem "rspec" + end + end + + it "removes gems and empty source blocks" do + gemfile <<-G + source "file://#{gem_repo1}" + + gem "rack" + + source "file://#{gem_repo3}" do + gem "rspec" + end + G + + bundle! "install" + + bundle! "remove rspec" + + expect(out).to include("rspec was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + gem "rack" + G + end + end + + describe "with eval_gemfile" do + context "when gems are present in both gemfiles" do + it "removes the gems" do + create_file "Gemfile-other", <<-G + gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + + gem "rack" + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + end + end + + context "when gems are present in other gemfile" do + it "removes the gems" do + create_file "Gemfile-other", <<-G + gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + G + + bundle! "remove rack" + + expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"") + expect(out).to include("rack was removed.") + end + end + + context "when gems to be removed are not specified in any of the gemfiles" do + it "throws error for the gems not present" do + # an empty gemfile + # indicating the gem is not present in the gemfile + create_file "Gemfile-other", <<-G + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + G + + bundle "remove rack" + + expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile")} so it could not be removed.") + end + end + + context "when the gem is present in parent file but not in gemfile specified by eval_gemfile" do + it "removes the gem" do + create_file "Gemfile-other", <<-G + gem "rails" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + gem "rack" + G + + bundle "remove rack" + + expect(out).to include("rack was removed.") + expect(out).to include("`rack` is not specified in #{bundled_app("Gemfile-other")} so it could not be removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + G + end + end + + context "when gems can not be removed from other gemfile" do + it "shows error" do + create_file "Gemfile-other", <<-G + gem "rails"; gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + gem "rack" + G + + bundle "remove rack" + + expect(out).to include("rack was removed.") + if Gem::VERSION >= "1.6.0" + expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.") + else + expect(out).to include("Gems could not be removed. rails (>= 0, runtime) would also have been removed.") + end + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + G + end + end + + context "when gems could not be removed from parent gemfile" do + it "shows error" do + create_file "Gemfile-other", <<-G + gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + gem "rails"; gem "rack" + G + + bundle "remove rack" + + if Gem::VERSION >= "1.6.0" + expect(out).to include("Gems could not be removed. rails (>= 0) would also have been removed.") + else + expect(out).to include("Gems could not be removed. rails (>= 0, runtime) would also have been removed.") + end + expect(bundled_app("Gemfile-other").read).to include("gem \"rack\"") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + gem "rails"; gem "rack" + G + end + end + + context "when gem present in gemfiles but could not be removed from one from one of them" do + it "removes gem which can be removed and shows warning for file from which it can not be removed" do + create_file "Gemfile-other", <<-G + gem "rack" + G + + install_gemfile <<-G + source "file://#{gem_repo1}" + + eval_gemfile "Gemfile-other" + gem"rack" + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + expect(bundled_app("Gemfile-other").read).to_not include("gem \"rack\"") + end + end + end + + context "with install_if" do + it "removes gems inside blocks and empty blocks" do + install_gemfile <<-G + source "file://#{gem_repo1}" + + install_if(lambda { false }) do + gem "rack" + end + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "with env" do + it "removes gems inside blocks and empty blocks" do + install_gemfile <<-G + source "file://#{gem_repo1}" + + env "BUNDLER_TEST" do + gem "rack" + end + G + + bundle! "remove rack" + + expect(out).to include("rack was removed.") + gemfile_should_be <<-G + source "file://#{gem_repo1}" + G + end + end + + context "with gemspec" do + it "should not remove the gem" do + build_lib("foo", :path => tmp.join("foo")) do |s| + s.write("foo.gemspec", "") + s.add_dependency "rack" + end + + install_gemfile(<<-G) + source "file://#{gem_repo1}" + gemspec :path => '#{tmp.join("foo")}', :name => 'foo' + G + + bundle! "remove foo" + + expect(out).to include("foo could not be removed.") + end + end +end diff --git a/spec/bundler/commands/show_spec.rb b/spec/bundler/commands/show_spec.rb new file mode 100644 index 0000000000..efbe4b13fb --- /dev/null +++ b/spec/bundler/commands/show_spec.rb @@ -0,0 +1,244 @@ +# frozen_string_literal: true + +RSpec.describe "bundle show", :bundler => "< 2", :ruby => ">= 2.0" do + context "with a standard Gemfile" do + before :each do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + end + + it "creates a Gemfile.lock if one did not exist" do + FileUtils.rm("Gemfile.lock") + + bundle "show" + + expect(bundled_app("Gemfile.lock")).to exist + end + + it "creates a Gemfile.lock when invoked with a gem name" do + FileUtils.rm("Gemfile.lock") + + bundle "show rails" + + expect(bundled_app("Gemfile.lock")).to exist + end + + it "prints path if gem exists in bundle" do + bundle "show rails" + expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) + end + + context "when show command deprecation is enabled" do + before { bundle "config major_deprecations yes" } + + it "prints path if gem exists in bundle" do + bundle "show rails" + expect(out).to eq( + "[DEPRECATED FOR 2.0] use `bundle info rails` instead of `bundle show rails`\n" + + default_bundle_path("gems", "rails-2.3.2").to_s + ) + end + + it "prints the path to the running bundler" do + bundle "show bundler" + expect(out).to eq( + "[DEPRECATED FOR 2.0] use `bundle info bundler` instead of `bundle show bundler`\n" + + root.to_s + ) + end + + it "prints path if gem exists in bundle (with --paths option)" do + bundle "show rails --paths" + expect(out).to eq( + "[DEPRECATED FOR 2.0] use `bundle info rails --path` instead of `bundle show rails --paths`\n" + + default_bundle_path("gems", "rails-2.3.2").to_s + ) + end + + it "prints path of all gems in bundle sorted by name" do + bundle "show --paths" + + expect(out).to include(default_bundle_path("gems", "rake-10.0.2").to_s) + expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s) + + out_lines = out.split("\n") + expect(out_lines[0]).to eq("[DEPRECATED FOR 2.0] use `bundle list` instead of `bundle show --paths`") + + # Gem names are the last component of their path. + gem_list = out_lines[1..-1].map {|p| p.split("/").last } + expect(gem_list).to eq(gem_list.sort) + end + end + + it "prints path if gem exists in bundle (with --paths option)" do + bundle "show rails --paths" + expect(out).to eq(default_bundle_path("gems", "rails-2.3.2").to_s) + end + + it "warns if path no longer exists on disk" do + FileUtils.rm_rf(default_bundle_path("gems", "rails-2.3.2")) + + bundle "show rails" + + expect(out).to match(/has been deleted/i). + and include(default_bundle_path("gems", "rails-2.3.2").to_s) + end + + it "prints the path to the running bundler" do + bundle "show bundler" + expect(out).to eq(root.to_s) + end + + it "complains if gem not in bundle" do + bundle "show missing" + expect(out).to match(/could not find gem 'missing'/i) + end + + it "prints path of all gems in bundle sorted by name" do + bundle "show --paths" + + expect(out).to include(default_bundle_path("gems", "rake-10.0.2").to_s) + expect(out).to include(default_bundle_path("gems", "rails-2.3.2").to_s) + + # Gem names are the last component of their path. + gem_list = out.split.map {|p| p.split("/").last } + expect(gem_list).to eq(gem_list.sort) + end + + it "prints summary of gems" do + bundle "show --verbose" + + loaded_bundler_spec = Bundler.load.specs["bundler"] + expected = if !loaded_bundler_spec.empty? + loaded_bundler_spec[0].homepage + else + "No website available." + end + + expect(out).to include("* actionmailer (2.3.2)") + expect(out).to include("\tSummary: This is just a fake gem for testing") + expect(out).to include("\tHomepage: #{expected}") + expect(out).to include("\tStatus: Up to date") + end + end + + context "with a git repo in the Gemfile" do + before :each do + @git = build_git "foo", "1.0" + end + + it "prints out git info" do + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}" + G + expect(the_bundle).to include_gems "foo 1.0" + + bundle :show + expect(out).to include("foo (1.0 #{@git.ref_for("master", 6)}") + end + + it "prints out branch names other than master" do + update_git "foo", :branch => "omg" do |s| + s.write "lib/foo.rb", "FOO = '1.0.omg'" + end + @revision = revision_for(lib_path("foo-1.0"))[0...6] + + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}", :branch => "omg" + G + expect(the_bundle).to include_gems "foo 1.0.omg" + + bundle :show + expect(out).to include("foo (1.0 #{@git.ref_for("omg", 6)}") + end + + it "doesn't print the branch when tied to a ref" do + sha = revision_for(lib_path("foo-1.0")) + install_gemfile <<-G + gem "foo", :git => "#{lib_path("foo-1.0")}", :ref => "#{sha}" + G + + bundle :show + expect(out).to include("foo (1.0 #{sha[0..6]})") + end + + it "handles when a version is a '-' prerelease", :rubygems => "2.1" do + @git = build_git("foo", "1.0.0-beta.1", :path => lib_path("foo")) + install_gemfile <<-G + gem "foo", "1.0.0-beta.1", :git => "#{lib_path("foo")}" + G + expect(the_bundle).to include_gems "foo 1.0.0.pre.beta.1" + + bundle! :show + expect(out).to include("foo (1.0.0.pre.beta.1") + end + end + + context "in a fresh gem in a blank git repo" do + before :each do + build_git "foo", :path => lib_path("foo") + in_app_root_custom lib_path("foo") + File.open("Gemfile", "w") {|f| f.puts "gemspec" } + sys_exec "rm -rf .git && git init" + end + + it "does not output git errors" do + bundle :show + expect(err).to lack_errors + end + end + + it "performs an automatic bundle install" do + gemfile <<-G + source "file://#{gem_repo1}" + gem "foo" + G + + bundle "config auto_install 1" + bundle :show + expect(out).to include("Installing foo 1.0") + end + + context "with an invalid regexp for gem name" do + it "does not find the gem" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rails" + G + + invalid_regexp = "[]" + + bundle "show #{invalid_regexp}" + expect(out).to include("Could not find gem '#{invalid_regexp}'.") + end + end + + context "--outdated option" do + # Regression test for https://github.com/bundler/bundler/issues/5375 + before do + build_repo2 + end + + it "doesn't update gems to newer versions" do + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "rails" + G + + expect(the_bundle).to include_gem("rails 2.3.2") + + update_repo2 do + build_gem "rails", "3.0.0" do |s| + s.executables = "rails" + end + end + + bundle! "show --outdated" + + bundle! "install" + expect(the_bundle).to include_gem("rails 2.3.2") + end + end +end diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb new file mode 100644 index 0000000000..6eb49d3acd --- /dev/null +++ b/spec/bundler/commands/update_spec.rb @@ -0,0 +1,943 @@ +# frozen_string_literal: true + +RSpec.describe "bundle update" do + before :each do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + G + end + + describe "with no arguments", :bundler => "< 2" do + it "updates the entire bundle" do + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle "update" + expect(out).to include("Bundle updated!") + expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + end + + it "doesn't delete the Gemfile.lock file if something goes wrong" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + exit! + G + bundle "update" + expect(bundled_app("Gemfile.lock")).to exist + end + end + + describe "with --all", :bundler => "2" do + it "updates the entire bundle" do + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle! "update", :all => true + expect(out).to include("Bundle updated!") + expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 3.0" + end + + it "doesn't delete the Gemfile.lock file if something goes wrong" do + gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + gem "rack-obama" + exit! + G + bundle "update", :all => true + expect(bundled_app("Gemfile.lock")).to exist + end + end + + describe "with --gemfile" do + it "creates lock files based on the Gemfile name" do + gemfile bundled_app("OmgFile"), <<-G + source "file://#{gem_repo1}" + gem "rack", "1.0" + G + + bundle! "update --gemfile OmgFile", :all => bundle_update_requires_all? + + expect(bundled_app("OmgFile.lock")).to exist + end + end + + context "when update_requires_all_flag is set" do + before { bundle! "config update_requires_all_flag true" } + + it "errors when passed nothing" do + install_gemfile! "" + bundle :update + expect(out).to eq("To update everything, pass the `--all` flag.") + end + + it "errors when passed --all and another option" do + install_gemfile! "" + bundle "update --all foo" + expect(out).to eq("Cannot specify --all along with specific options.") + end + + it "updates everything when passed --all" do + install_gemfile! "" + bundle "update --all" + expect(out).to include("Bundle updated!") + end + end + + describe "--quiet argument" do + it "hides UI messages" do + bundle "update --quiet" + expect(out).not_to include("Bundle updated!") + end + end + + describe "with a top level dependency" do + it "unlocks all child dependencies that are unrelated to other locked dependencies" do + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle "update rack-obama" + expect(the_bundle).to include_gems "rack 1.2", "rack-obama 1.0", "activesupport 2.3.5" + end + end + + describe "with an unknown dependency" do + it "should inform the user" do + bundle "update halting-problem-solver" + expect(out).to include "Could not find gem 'halting-problem-solver'" + end + it "should suggest alternatives" do + bundle "update active-support" + expect(out).to include "Did you mean activesupport?" + end + end + + describe "with a child dependency" do + it "should update the child dependency" do + update_repo2 + bundle "update rack" + expect(the_bundle).to include_gems "rack 1.2" + end + end + + describe "when a possible resolve requires an older version of a locked gem" do + context "and only_update_to_newer_versions is set" do + before do + bundle! "config only_update_to_newer_versions true" + end + + it "does not go to an older version" do + build_repo4 do + build_gem "tilt", "2.0.8" + build_gem "slim", "3.0.9" do |s| + s.add_dependency "tilt", [">= 1.3.3", "< 2.1"] + end + build_gem "slim_lint", "0.16.1" do |s| + s.add_dependency "slim", [">= 3.0", "< 5.0"] + end + build_gem "slim-rails", "0.2.1" do |s| + s.add_dependency "slim", ">= 0.9.2" + end + build_gem "slim-rails", "3.1.3" do |s| + s.add_dependency "slim", "~> 3.0" + end + end + + install_gemfile! <<-G + source "file:#{gem_repo4}" + gem "slim-rails" + gem "slim_lint" + G + + expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + + update_repo4 do + build_gem "slim", "4.0.0" do |s| + s.add_dependency "tilt", [">= 2.0.6", "< 2.1"] + end + end + + bundle! "update", :all => bundle_update_requires_all? + + expect(the_bundle).to include_gems("slim 3.0.9", "slim-rails 3.1.3", "slim_lint 0.16.1") + end + + it "should still downgrade if forced by the Gemfile" do + build_repo4 do + build_gem "a" + build_gem "b", "1.0" + build_gem "b", "2.0" + end + + install_gemfile! <<-G + source "file:#{gem_repo4}" + gem "a" + gem "b" + G + + expect(the_bundle).to include_gems("a 1.0", "b 2.0") + + gemfile <<-G + source "file://#{gem_repo4}" + gem "a" + gem "b", "1.0" + G + + bundle! "update b" + + expect(the_bundle).to include_gems("a 1.0", "b 1.0") + end + end + end + + describe "with --local option" do + it "doesn't hit repo2" do + FileUtils.rm_rf(gem_repo2) + + bundle "update --local --all" + expect(out).not_to include("Fetching source index") + end + end + + describe "with --group option" do + it "should update only specified group gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", :group => :development + gem "rack" + G + update_repo2 do + build_gem "activesupport", "3.0" + end + bundle "update --group development" + expect(the_bundle).to include_gems "activesupport 3.0" + expect(the_bundle).not_to include_gems "rack 1.2" + end + + context "when conservatively updating a group with non-group sub-deps" do + it "should update only specified group gems" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activemerchant", :group => :development + gem "activesupport" + G + update_repo2 do + build_gem "activemerchant", "2.0" + build_gem "activesupport", "3.0" + end + bundle "update --conservative --group development" + expect(the_bundle).to include_gems "activemerchant 2.0" + expect(the_bundle).not_to include_gems "activesupport 3.0" + end + end + + context "when there is a source with the same name as a gem in a group" do + before :each do + build_git "foo", :path => lib_path("activesupport") + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport", :group => :development + gem "foo", :git => "#{lib_path("activesupport")}" + G + end + + it "should not update the gems from that source" do + update_repo2 { build_gem "activesupport", "3.0" } + update_git "foo", "2.0", :path => lib_path("activesupport") + + bundle "update --group development" + expect(the_bundle).to include_gems "activesupport 3.0" + expect(the_bundle).not_to include_gems "foo 2.0" + end + end + + context "when bundler itself is a transitive dependency" do + it "executes without error" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport", :group => :development + gem "rack" + G + update_repo2 do + build_gem "activesupport", "3.0" + end + bundle "update --group development" + expect(the_bundle).to include_gems "activesupport 2.3.5" + expect(the_bundle).to include_gems "bundler #{Bundler::VERSION}" + expect(the_bundle).not_to include_gems "rack 1.2" + end + end + end + + describe "in a frozen bundle" do + it "should fail loudly", :bundler => "< 2" do + bundle! "install --deployment" + bundle "update", :all => bundle_update_requires_all? + + expect(last_command).to be_failure + expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m) + expect(out).to match(/freeze \nby running `bundle install --no-deployment`./m) + end + + it "should suggest different command when frozen is set globally", :bundler => "< 2" do + bundle! "config --global frozen 1" + bundle "update", :all => bundle_update_requires_all? + expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). + and match(/freeze \nby running `bundle config --delete frozen`./m) + end + + it "should suggest different command when frozen is set globally", :bundler => "2" do + bundle! "config --global deployment true" + bundle "update", :all => bundle_update_requires_all? + expect(out).to match(/You are trying to install in deployment mode after changing.your Gemfile/m). + and match(/freeze \nby running `bundle config --delete deployment`./m) + end + end + + describe "with --source option" do + it "should not update gems not included in the source that happen to have the same name", :bundler => "< 2" do + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "activesupport" + G + update_repo2 { build_gem "activesupport", "3.0" } + + bundle! "update --source activesupport" + expect(the_bundle).to include_gem "activesupport 3.0" + end + + it "should not update gems not included in the source that happen to have the same name", :bundler => "2" do + install_gemfile! <<-G + source "file://#{gem_repo2}" + gem "activesupport" + G + update_repo2 { build_gem "activesupport", "3.0" } + + bundle! "update --source activesupport" + expect(the_bundle).not_to include_gem "activesupport 3.0" + end + + context "with unlock_source_unlocks_spec set to false" do + before { bundle! "config unlock_source_unlocks_spec false" } + + it "should not update gems not included in the source that happen to have the same name" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + G + update_repo2 { build_gem "activesupport", "3.0" } + + bundle "update --source activesupport" + expect(the_bundle).not_to include_gems "activesupport 3.0" + end + end + end + + context "when there is a child dependency that is also in the gemfile" do + before do + build_repo2 do + build_gem "fred", "1.0" + build_gem "harry", "1.0" do |s| + s.add_dependency "fred" + end + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "harry" + gem "fred" + G + end + + it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 2" do + update_repo2 do + build_gem "fred", "2.0" + build_gem "harry", "2.0" do |s| + s.add_dependency "fred" + end + end + + bundle "update --source harry" + expect(the_bundle).to include_gems "harry 2.0" + expect(the_bundle).to include_gems "fred 1.0" + end + + it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "2" do + update_repo2 do + build_gem "fred", "2.0" + build_gem "harry", "2.0" do |s| + s.add_dependency "fred" + end + end + + bundle "update --source harry" + expect(the_bundle).to include_gems "harry 1.0", "fred 1.0" + end + end + + context "when there is a child dependency that appears elsewhere in the dependency graph" do + before do + build_repo2 do + build_gem "fred", "1.0" do |s| + s.add_dependency "george" + end + build_gem "george", "1.0" + build_gem "harry", "1.0" do |s| + s.add_dependency "george" + end + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "harry" + gem "fred" + G + end + + it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "< 2" do + update_repo2 do + build_gem "george", "2.0" + build_gem "harry", "2.0" do |s| + s.add_dependency "george" + end + end + + bundle "update --source harry" + expect(the_bundle).to include_gems "harry 2.0" + expect(the_bundle).to include_gems "fred 1.0" + expect(the_bundle).to include_gems "george 1.0" + end + + it "should not update the child dependencies of a gem that has the same name as the source", :bundler => "2" do + update_repo2 do + build_gem "george", "2.0" + build_gem "harry", "2.0" do |s| + s.add_dependency "george" + end + end + + bundle "update --source harry" + expect(the_bundle).to include_gems "harry 1.0", "fred 1.0", "george 1.0" + end + end +end + +RSpec.describe "bundle update in more complicated situations" do + before :each do + build_repo2 + end + + it "will eagerly unlock dependencies of a specified gem" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + gem "thin" + gem "rack-obama" + G + + update_repo2 do + build_gem "thin", "2.0" do |s| + s.add_dependency "rack" + end + end + + bundle "update thin" + expect(the_bundle).to include_gems "thin 2.0", "rack 1.2", "rack-obama 1.0" + end + + it "will warn when some explicitly updated gems are not updated" do + install_gemfile! <<-G + source "file:#{gem_repo2}" + + gem "thin" + gem "rack-obama" + G + + update_repo2 do + build_gem("thin", "2.0") {|s| s.add_dependency "rack" } + build_gem "rack", "10.0" + end + + bundle! "update thin rack-obama" + expect(last_command.stdboth).to include "Bundler attempted to update rack-obama but its version stayed the same" + expect(the_bundle).to include_gems "thin 2.0", "rack 10.0", "rack-obama 1.0" + end + + it "will update only from pinned source" do + install_gemfile <<-G + source "file://#{gem_repo2}" + + source "file://#{gem_repo1}" do + gem "thin" + end + G + + update_repo2 do + build_gem "thin", "2.0" + end + + bundle "update" + expect(the_bundle).to include_gems "thin 1.0" + end + + context "when the lockfile is for a different platform" do + before do + build_repo4 do + build_gem("a", "0.9") + build_gem("a", "0.9") {|s| s.platform = "java" } + build_gem("a", "1.1") + build_gem("a", "1.1") {|s| s.platform = "java" } + end + + gemfile <<-G + source "file://#{gem_repo4}" + gem "a" + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + a (0.9-java) + + PLATFORMS + java + + DEPENDENCIES + a + L + + simulate_platform linux + end + + it "allows updating" do + bundle! :update, :all => true + expect(the_bundle).to include_gem "a 1.1" + end + + it "allows updating a specific gem" do + bundle! "update a" + expect(the_bundle).to include_gem "a 1.1" + end + end +end + +RSpec.describe "bundle update without a Gemfile.lock" do + it "should not explode" do + build_repo2 + + gemfile <<-G + source "file://#{gem_repo2}" + + gem "rack", "1.0" + G + + bundle "update", :all => bundle_update_requires_all? + + expect(the_bundle).to include_gems "rack 1.0.0" + end +end + +RSpec.describe "bundle update when a gem depends on a newer version of bundler" do + before(:each) do + build_repo2 do + build_gem "rails", "3.0.1" do |s| + s.add_dependency "bundler", Bundler::VERSION.succ + end + end + + gemfile <<-G + source "file://#{gem_repo2}" + gem "rails", "3.0.1" + G + end + + it "should explain that bundler conflicted", :bundler => "< 2" do + bundle "update", :all => bundle_update_requires_all? + expect(last_command.stdboth).not_to match(/in snapshot/i) + expect(last_command.bundler_err).to match(/current Bundler version/i). + and match(/perhaps you need to update bundler/i) + end + + it "should warn that the newer version of Bundler would conflict", :bundler => "2" do + bundle! "update", :all => true + expect(last_command.bundler_err).to include("rails (3.0.1) has dependency bundler"). + and include("so the dependency is being ignored") + expect(the_bundle).to include_gem "rails 3.0.1" + end +end + +RSpec.describe "bundle update" do + it "shows the previous version of the gem when updated from rubygems source", :bundler => "< 2" do + build_repo2 + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + G + + bundle "update", :all => bundle_update_requires_all? + expect(out).to include("Using activesupport 2.3.5") + + update_repo2 do + build_gem "activesupport", "3.0" + end + + bundle "update", :all => bundle_update_requires_all? + expect(out).to include("Installing activesupport 3.0 (was 2.3.5)") + end + + context "with suppress_install_using_messages set" do + before { bundle! "config suppress_install_using_messages true" } + + it "only prints `Using` for versions that have changed" do + build_repo4 do + build_gem "bar" + build_gem "foo" + end + + install_gemfile! <<-G + source "file://#{gem_repo4}" + gem "bar" + gem "foo" + G + + bundle! "update", :all => bundle_update_requires_all? + out.gsub!(/RubyGems [\d\.]+ is not threadsafe.*\n?/, "") + expect(out).to include "Resolving dependencies...\nBundle updated!" + + update_repo4 do + build_gem "foo", "2.0" + end + + bundle! "update", :all => bundle_update_requires_all? + out.sub!("Removing foo (1.0)\n", "") + out.gsub!(/RubyGems [\d\.]+ is not threadsafe.*\n?/, "") + expect(out).to include strip_whitespace(<<-EOS).strip + Resolving dependencies... + Fetching foo 2.0 (was 1.0) + Installing foo 2.0 (was 1.0) + Bundle updated + EOS + end + end + + it "shows error message when Gemfile.lock is not preset and gem is specified" do + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "activesupport" + G + + bundle "update nonexisting" + expect(out).to include("This Bundle hasn't been installed yet. Run `bundle install` to update and install the bundled gems.") + expect(exitstatus).to eq(22) if exitstatus + end +end + +RSpec.describe "bundle update --ruby" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '2.1.3' + ::RUBY_PATCHLEVEL = 100 + ruby '~> 2.1.0' + G + bundle "update --ruby" + end + + context "when the Gemfile removes the ruby" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '2.1.4' + ::RUBY_PATCHLEVEL = 222 + G + end + it "removes the Ruby from the Gemfile.lock" do + bundle "update --ruby" + + lockfile_should_be <<-L + GEM + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when the Gemfile specified an updated Ruby version" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '2.1.4' + ::RUBY_PATCHLEVEL = 222 + ruby '~> 2.1.0' + G + end + it "updates the Gemfile.lock with the latest version" do + bundle "update --ruby" + + lockfile_should_be <<-L + GEM + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + RUBY VERSION + ruby 2.1.4p222 + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end + + context "when a different Ruby is being used than has been versioned" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '2.2.2' + ::RUBY_PATCHLEVEL = 505 + ruby '~> 2.1.0' + G + end + it "shows a helpful error message" do + bundle "update --ruby" + + expect(out).to include("Your Ruby version is 2.2.2, but your Gemfile specified ~> 2.1.0") + end + end + + context "when updating Ruby version and Gemfile `ruby`" do + before do + install_gemfile <<-G + ::RUBY_VERSION = '1.8.3' + ::RUBY_PATCHLEVEL = 55 + ruby '~> 1.8.0' + G + end + it "updates the Gemfile.lock with the latest version" do + bundle "update --ruby" + + lockfile_should_be <<-L + GEM + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + + RUBY VERSION + ruby 1.8.3p55 + + BUNDLED WITH + #{Bundler::VERSION} + L + end + end +end + +RSpec.describe "bundle update --bundler" do + it "updates the bundler version in the lockfile without re-resolving" do + build_repo4 do + build_gem "rack", "1.0" + end + + install_gemfile! <<-G + source "file:#{gem_repo4}" + gem "rack" + G + lockfile lockfile.sub(/(^\s*)#{Bundler::VERSION}($)/, '\11.0.0\2') + + FileUtils.rm_r gem_repo4 + + bundle! :update, :bundler => true, :verbose => true + expect(the_bundle).to include_gem "rack 1.0" + + expect(the_bundle.locked_gems.bundler_version).to eq v(Bundler::VERSION) + end +end + +# these specs are slow and focus on integration and therefore are not exhaustive. unit specs elsewhere handle that. +RSpec.describe "bundle update conservative" do + context "patch and minor options" do + before do + build_repo4 do + build_gem "foo", %w[1.4.3 1.4.4] do |s| + s.add_dependency "bar", "~> 2.0" + end + build_gem "foo", %w[1.4.5 1.5.0] do |s| + s.add_dependency "bar", "~> 2.1" + end + build_gem "foo", %w[1.5.1] do |s| + s.add_dependency "bar", "~> 3.0" + end + build_gem "bar", %w[2.0.3 2.0.4 2.0.5 2.1.0 2.1.1 3.0.0] + build_gem "qux", %w[1.0.0 1.0.1 1.1.0 2.0.0] + end + + # establish a lockfile set to 1.4.3 + install_gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo', '1.4.3' + gem 'bar', '2.0.3' + gem 'qux', '1.0.0' + G + + # remove 1.4.3 requirement and bar altogether + # to setup update specs below + gemfile <<-G + source "file://#{gem_repo4}" + gem 'foo' + gem 'qux' + G + end + + context "patch preferred" do + it "single gem updates dependent gem to minor" do + bundle! "update --patch foo" + + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.0" + end + + it "update all" do + bundle! "update --patch", :all => bundle_update_requires_all? + + expect(the_bundle).to include_gems "foo 1.4.5", "bar 2.1.1", "qux 1.0.1" + end + end + + context "minor preferred" do + it "single gem updates dependent gem to major" do + bundle! "update --minor foo" + + expect(the_bundle).to include_gems "foo 1.5.1", "bar 3.0.0", "qux 1.0.0" + end + end + + context "strict" do + it "patch preferred" do + bundle! "update --patch foo bar --strict" + + expect(the_bundle).to include_gems "foo 1.4.4", "bar 2.0.5", "qux 1.0.0" + end + + it "minor preferred" do + bundle! "update --minor --strict", :all => bundle_update_requires_all? + + expect(the_bundle).to include_gems "foo 1.5.0", "bar 2.1.1", "qux 1.1.0" + end + end + end + + context "eager unlocking" do + before do + build_repo4 do + build_gem "isolated_owner", %w[1.0.1 1.0.2] do |s| + s.add_dependency "isolated_dep", "~> 2.0" + end + build_gem "isolated_dep", %w[2.0.1 2.0.2] + + build_gem "shared_owner_a", %w[3.0.1 3.0.2] do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_owner_b", %w[4.0.1 4.0.2] do |s| + s.add_dependency "shared_dep", "~> 5.0" + end + build_gem "shared_dep", %w[5.0.1 5.0.2] + end + + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner' + + gem 'shared_owner_a' + gem 'shared_owner_b' + G + + lockfile <<-L + GEM + remote: file://#{gem_repo4} + specs: + isolated_dep (2.0.1) + isolated_owner (1.0.1) + isolated_dep (~> 2.0) + shared_dep (5.0.1) + shared_owner_a (3.0.1) + shared_dep (~> 5.0) + shared_owner_b (4.0.1) + shared_dep (~> 5.0) + + PLATFORMS + ruby + + DEPENDENCIES + shared_owner_a + shared_owner_b + isolated_owner + + BUNDLED WITH + 1.13.0 + L + end + + it "should eagerly unlock isolated dependency" do + bundle "update isolated_owner" + + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.1", "shared_owner_b 4.0.1" + end + + it "should eagerly unlock shared dependency" do + bundle "update shared_owner_a" + + expect(the_bundle).to include_gems "isolated_owner 1.0.1", "isolated_dep 2.0.1", "shared_dep 5.0.2", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + + it "should not eagerly unlock with --conservative" do + bundle "update --conservative shared_owner_a isolated_owner" + + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + + it "should match bundle install conservative update behavior when not eagerly unlocking" do + gemfile <<-G + source "file://#{gem_repo4}" + gem 'isolated_owner', '1.0.2' + + gem 'shared_owner_a', '3.0.2' + gem 'shared_owner_b' + G + + bundle "install" + + expect(the_bundle).to include_gems "isolated_owner 1.0.2", "isolated_dep 2.0.2", "shared_dep 5.0.1", "shared_owner_a 3.0.2", "shared_owner_b 4.0.1" + end + end + + context "error handling" do + before do + gemfile "" + end + + it "raises if too many flags are provided" do + bundle "update --patch --minor", :all => bundle_update_requires_all? + + expect(last_command.bundler_err).to eq "Provide only one of the following options: minor, patch" + end + end +end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb new file mode 100644 index 0000000000..b919c25e0f --- /dev/null +++ b/spec/bundler/commands/version_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +RSpec.describe "bundle version" do + context "with -v" do + it "outputs the version", :bundler => "< 2" do + bundle! "-v" + expect(out).to eq("Bundler version #{Bundler::VERSION}") + end + + it "outputs the version", :bundler => "2" do + bundle! "-v" + expect(out).to eq(Bundler::VERSION) + end + end + + context "with --version" do + it "outputs the version", :bundler => "< 2" do + bundle! "--version" + expect(out).to eq("Bundler version #{Bundler::VERSION}") + end + + it "outputs the version", :bundler => "2" do + bundle! "--version" + expect(out).to eq(Bundler::VERSION) + end + end + + context "with version" do + it "outputs the version with build metadata", :bundler => "< 2" do + bundle! "version" + expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit [a-fA-F0-9]{7,}\)\z/) + end + + it "outputs the version with build metadata", :bundler => "2" do + bundle! "version" + expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit [a-fA-F0-9]{7,}\)\z/) + end + end +end diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb new file mode 100644 index 0000000000..3804d3561c --- /dev/null +++ b/spec/bundler/commands/viz_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +RSpec.describe "bundle viz", :ruby => "1.9.3", :bundler => "< 2", :if => Bundler.which("dot") do + let(:ruby_graphviz) do + graphviz_glob = base_system_gems.join("cache/ruby-graphviz*") + Pathname.glob(graphviz_glob).first + end + + before do + system_gems ruby_graphviz + end + + it "graphs gems from the Gemfile" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + bundle! "viz" + expect(out).to include("gem_graph.png") + + bundle! "viz", :format => "debug" + expect(out).to eq(strip_whitespace(<<-DOT).strip) + digraph Gemfile { + concentrate = "true"; + normalize = "true"; + nodesep = "0.55"; + edge[ weight = "2"]; + node[ fontname = "Arial, Helvetica, SansSerif"]; + edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; + default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; + rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"]; + default -> rack [constraint = "false"]; + "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"]; + default -> "rack-obama" [constraint = "false"]; + "rack-obama" -> rack; + } + debugging bundle viz... + DOT + end + + it "graphs gems that are prereleases" do + build_repo2 do + build_gem "rack", "1.3.pre" + end + + install_gemfile <<-G + source "file://#{gem_repo2}" + gem "rack", "= 1.3.pre" + gem "rack-obama" + G + + bundle! "viz" + expect(out).to include("gem_graph.png") + + bundle! "viz", :format => :debug, :version => true + expect(out).to eq(strip_whitespace(<<-EOS).strip) + digraph Gemfile { + concentrate = "true"; + normalize = "true"; + nodesep = "0.55"; + edge[ weight = "2"]; + node[ fontname = "Arial, Helvetica, SansSerif"]; + edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; + default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; + rack [style = "filled", fillcolor = "#B9B9D5", label = "rack\\n1.3.pre"]; + default -> rack [constraint = "false"]; + "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama\\n1.0"]; + default -> "rack-obama" [constraint = "false"]; + "rack-obama" -> rack; + } + debugging bundle viz... + EOS + end + + context "with another gem that has a graphviz file" do + before do + build_repo4 do + build_gem "graphviz", "999" do |s| + s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") + end + end + + system_gems ruby_graphviz, "graphviz-999", :gem_repo => gem_repo4 + end + + it "loads the correct ruby-graphviz gem" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "rack" + gem "rack-obama" + G + + bundle! "viz", :format => "debug" + expect(out).to eq(strip_whitespace(<<-DOT).strip) + digraph Gemfile { + concentrate = "true"; + normalize = "true"; + nodesep = "0.55"; + edge[ weight = "2"]; + node[ fontname = "Arial, Helvetica, SansSerif"]; + edge[ fontname = "Arial, Helvetica, SansSerif" , fontsize = "12"]; + default [style = "filled", fillcolor = "#B9B9D5", shape = "box3d", fontsize = "16", label = "default"]; + rack [style = "filled", fillcolor = "#B9B9D5", label = "rack"]; + default -> rack [constraint = "false"]; + "rack-obama" [style = "filled", fillcolor = "#B9B9D5", label = "rack-obama"]; + default -> "rack-obama" [constraint = "false"]; + "rack-obama" -> rack; + } + debugging bundle viz... + DOT + end + end + + context "--without option" do + it "one group" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport" + + group :rails do + gem "rails" + end + G + + bundle! "viz --without=rails" + expect(out).to include("gem_graph.png") + end + + it "two groups" do + install_gemfile <<-G + source "file://#{gem_repo1}" + gem "activesupport" + + group :rack do + gem "rack" + end + + group :rails do + gem "rails" + end + G + + bundle! "viz --without=rails:rack" + expect(out).to include("gem_graph.png") + end + end +end -- cgit v1.2.3