diff options
Diffstat (limited to 'spec/bundler/plugins')
| -rw-r--r-- | spec/bundler/plugins/command_spec.rb | 112 | ||||
| -rw-r--r-- | spec/bundler/plugins/hook_spec.rb | 331 | ||||
| -rw-r--r-- | spec/bundler/plugins/install_spec.rb | 466 | ||||
| -rw-r--r-- | spec/bundler/plugins/list_spec.rb | 60 | ||||
| -rw-r--r-- | spec/bundler/plugins/source/example_spec.rb | 456 | ||||
| -rw-r--r-- | spec/bundler/plugins/source_spec.rb | 111 | ||||
| -rw-r--r-- | spec/bundler/plugins/uninstall_spec.rb | 74 |
7 files changed, 1610 insertions, 0 deletions
diff --git a/spec/bundler/plugins/command_spec.rb b/spec/bundler/plugins/command_spec.rb new file mode 100644 index 0000000000..05d535a70c --- /dev/null +++ b/spec/bundler/plugins/command_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +RSpec.describe "command plugins" do + before do + build_repo2 do + build_plugin "command-mah" do |s| + s.write "plugins.rb", <<-RUBY + module Mah + class Plugin < Bundler::Plugin::API + command "mahcommand" # declares the command + + def exec(command, args) + puts "MahHello" + end + end + end + RUBY + end + end + + bundle "plugin install command-mah --source https://gem.repo2" + end + + it "executes without arguments" do + expect(out).to include("Installed plugin command-mah") + + bundle "mahcommand" + expect(out).to eq("MahHello") + end + + it "accepts the arguments" do + update_repo2 do + build_plugin "the-echoer" do |s| + s.write "plugins.rb", <<-RUBY + module Resonance + class Echoer + # Another method to declare the command + Bundler::Plugin::API.command "echo", self + + def exec(command, args) + puts "You gave me \#{args.join(", ")}" + end + end + end + RUBY + end + end + + bundle "plugin install the-echoer --source https://gem.repo2" + expect(out).to include("Installed plugin the-echoer") + + bundle "echo tacos tofu lasange" + expect(out).to eq("You gave me tacos, tofu, lasange") + end + + it "passes help flag to plugin" do + update_repo2 do + build_plugin "helpful" do |s| + s.write "plugins.rb", <<-RUBY + module Helpful + class Command + Bundler::Plugin::API.command "greet", self + + def exec(command, args) + if args.include?("--help") || args.include?("-h") + puts "Usage: bundle greet [NAME]" + else + puts "Hello" + end + end + end + end + RUBY + end + end + + bundle "plugin install helpful --source https://gem.repo2" + expect(out).to include("Installed plugin helpful") + + bundle "greet --help" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "greet -h" + expect(out).to eq("Usage: bundle greet [NAME]") + + bundle "help greet" + expect(out).to eq("Usage: bundle greet [NAME]") + end + + it "raises error on redeclaration of command" do + update_repo2 do + build_plugin "copycat" do |s| + s.write "plugins.rb", <<-RUBY + module CopyCat + class Cheater < Bundler::Plugin::API + command "mahcommand", self + + def exec(command, args) + end + end + end + RUBY + end + end + + bundle "plugin install copycat --source https://gem.repo2", raise_on_error: false + + expect(out).not_to include("Installed plugin copycat") + + expect(err).to include("Failed to install plugin `copycat`, due to Bundler::Plugin::Index::CommandConflict (Command(s) `mahcommand` declared by copycat are already registered.)") + end +end diff --git a/spec/bundler/plugins/hook_spec.rb b/spec/bundler/plugins/hook_spec.rb new file mode 100644 index 0000000000..ad8a4daeff --- /dev/null +++ b/spec/bundler/plugins/hook_spec.rb @@ -0,0 +1,331 @@ +# frozen_string_literal: true + +RSpec.describe "hook plugins" do + context "before-install-all hook" do + before do + build_repo2 do + build_plugin "before-install-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL_ALL do |deps| + puts "gems to be installed \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install before-install-all-plugin --source https://gem.repo2" + end + + it "runs before all rubygems are installed" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "gems to be installed rake, myrack" + end + end + + context "before-install hook" do + before do + build_repo2 do + build_plugin "before-install-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_INSTALL do |spec_install| + puts "installing gem \#{spec_install.name}" + end + RUBY + end + end + + bundle "plugin install before-install-plugin --source https://gem.repo2" + end + + it "runs before each rubygem is installed" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "installing gem rake" + expect(out).to include "installing gem myrack" + end + end + + context "after-install-all hook" do + before do + build_repo2 do + build_plugin "after-install-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL_ALL do |deps| + puts "installed gems \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-install-all-plugin --source https://gem.repo2" + end + + it "runs after each all rubygems are installed" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "installed gems rake, myrack" + end + end + + context "after-install hook" do + before do + build_repo2 do + build_plugin "after-install-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_INSTALL do |spec_install| + puts "installed gem \#{spec_install.name} : \#{spec_install.state}" + end + RUBY + end + end + + bundle "plugin install after-install-plugin --source https://gem.repo2" + end + + it "runs after each rubygem is installed" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "installed gem rake : installed" + expect(out).to include "installed gem myrack : installed" + end + end + + context "before-require-all hook" do + before do + build_repo2 do + build_plugin "before-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE_ALL do |deps| + puts "gems to be required \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install before-require-all-plugin --source https://gem.repo2" + end + + it "runs before all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "gems to be required rake, myrack" + end + end + + context "before-require hook" do + before do + build_repo2 do + build_plugin "before-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_REQUIRE do |dep| + puts "requiring gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install before-require-plugin --source https://gem.repo2" + end + + it "runs before each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "requiring gem rake" + expect(out).to include "requiring gem myrack" + end + end + + context "after-require-all hook" do + before do + build_repo2 do + build_plugin "after-require-all-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE_ALL do |deps| + puts "required gems \#{deps.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-require-all-plugin --source https://gem.repo2" + end + + it "runs after all rubygems are required" do + install_gemfile_and_bundler_require + expect(out).to include "required gems rake, myrack" + end + end + + context "after-require hook" do + before do + build_repo2 do + build_plugin "after-require-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_REQUIRE do |dep| + puts "required gem \#{dep.name}" + end + RUBY + end + end + + bundle "plugin install after-require-plugin --source https://gem.repo2" + end + + it "runs after each rubygem is required" do + install_gemfile_and_bundler_require + expect(out).to include "required gem rake" + expect(out).to include "required gem myrack" + end + end + + context "before-eval hook" do + before do + build_repo2 do + build_plugin "before-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_EVAL do |gemfile, lockfile| + puts "hooked eval start of \#{File.basename(gemfile)} to \#{File.basename(lockfile)}" + end + RUBY + end + end + + bundle "plugin install before-eval-plugin --source https://gem.repo2" + end + + it "runs before the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + G + + expect(out).to include "hooked eval start of Gemfile to Gemfile.lock" + end + end + + context "after-eval hook" do + before do + build_repo2 do + build_plugin "after-eval-plugin" do |s| + s.write "plugins.rb", <<-RUBY + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_EVAL do |defn| + puts "hooked eval after with gems \#{defn.dependencies.map(&:name).join(", ")}" + end + RUBY + end + end + + bundle "plugin install after-eval-plugin --source https://gem.repo2" + end + + it "runs after the Gemfile is evaluated" do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + gem "rake" + G + + expect(out).to include "hooked eval after with gems myrack, rake" + end + end + + context "before-fetch and after-fetch hooks" do + before do + build_repo2 do + build_plugin "fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_BEFORE_FETCH do |spec| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GEM_AFTER_FETCH do |spec| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "gem \#{spec.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each gem download" do + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + expect(out).to include "gem rake started fetch at" + expect(out).to match(/gem rake took \d+\.\d+ to fetch/) + expect(out).to include "gem myrack started fetch at" + expect(out).to match(/gem myrack took \d+\.\d+ to fetch/) + end + end + + context "before-git-fetch and after-git-fetch hooks" do + before do + build_repo2 do + build_plugin "git-fetch-timing-plugin" do |s| + s.write "plugins.rb", <<-RUBY + @timing_start = nil + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_BEFORE_FETCH do |source| + @timing_start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} started fetch at \#{@timing_start}" + end + Bundler::Plugin::API.hook Bundler::Plugin::Events::GIT_AFTER_FETCH do |source| + timing_end = Process.clock_gettime(Process::CLOCK_MONOTONIC) + puts "git source \#{source.name} took \#{timing_end - @timing_start} to fetch" + @timing_start = nil + end + RUBY + end + end + + bundle "plugin install git-fetch-timing-plugin --source https://gem.repo2" + end + + it "runs around each git source fetch" do + build_git "foo", "1.0", path: lib_path("foo") + + relative_path = lib_path("foo").relative_path_from(bundled_app) + install_gemfile <<-G, verbose: true + source "https://gem.repo1" + gem "foo", :git => "#{relative_path}" + G + + expect(out).to include "git source foo started fetch at" + expect(out).to match(/git source foo took \d+\.\d+ to fetch/) + end + end + + def install_gemfile_and_bundler_require + install_gemfile <<-G + source "https://gem.repo1" + gem "rake" + gem "myrack" + G + + ruby <<-RUBY + require "bundler" + Bundler.require + RUBY + end +end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb new file mode 100644 index 0000000000..dcacf764be --- /dev/null +++ b/spec/bundler/plugins/install_spec.rb @@ -0,0 +1,466 @@ +# frozen_string_literal: true + +RSpec.describe "bundler plugin install" do + before do + build_repo2 do + build_plugin "foo" + build_plugin "kung-foo" + end + end + + it "shows proper message when gem in not found in the source" do + bundle "plugin install no-foo --source https://gem.repo1", raise_on_error: false + + expect(err).to include("Could not find") + plugin_should_not_be_installed("no-foo") + end + + it "installs from rubygems source" do + bundle "plugin install foo --source https://gem.repo2" + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + + it "installs from rubygems source in frozen mode" do + bundle "plugin install foo --source https://gem.repo2", env: { "BUNDLE_DEPLOYMENT" => "true" } + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + + it "installs from sources configured as Gem.sources without any flags" do + bundle "plugin install foo", artifice: "compact_index", env: { "BUNDLER_SPEC_GEM_SOURCES" => "https://gem.repo2" } + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + + it "shows help when --help flag is given" do + bundle "plugin install --help" + + # The help message defined in ../../lib/bundler/man/bundle-plugin.1.ronn will be output. + expect(out).to include("You can install, uninstall, and list plugin(s)") + end + + context "plugin is already installed" do + before do + bundle "plugin install foo --source https://gem.repo2" + end + + it "doesn't install plugin again" do + bundle "plugin install foo --source https://gem.repo2" + expect(out).not_to include("Installing plugin foo") + expect(out).not_to include("Installed plugin foo") + end + end + + it "installs multiple plugins" do + bundle "plugin install foo kung-foo --source https://gem.repo2" + + expect(out).to include("Installed plugin foo") + expect(out).to include("Installed plugin kung-foo") + + plugin_should_be_installed("foo", "kung-foo") + end + + it "uses the same version for multiple plugins" do + update_repo2 do + build_plugin "foo", "1.1" + build_plugin "kung-foo", "1.1" + end + + bundle "plugin install foo kung-foo --version '1.0' --source https://gem.repo2" + + expect(out).to include("Installing foo 1.0") + expect(out).to include("Installing kung-foo 1.0") + plugin_should_be_installed("foo", "kung-foo") + end + + it "installs the latest version if not installed" do + update_repo2 do + build_plugin "foo", "1.1" + end + + bundle "plugin install foo --version 1.0 --source https://gem.repo2 --verbose" + expect(out).to include("Installing foo 1.0") + + bundle "plugin install foo --source https://gem.repo2 --verbose" + expect(out).to include("Installing foo 1.1") + + bundle "plugin install foo --source https://gem.repo2 --verbose" + expect(out).to include("Using foo 1.1") + end + + it "raises an error when when --branch specified" do + bundle "plugin install foo --branch main --source https://gem.repo2", raise_on_error: false + + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("--branch can only be used with git sources") + end + + it "raises an error when --ref specified" do + bundle "plugin install foo --ref v1.2.3 --source https://gem.repo2", raise_on_error: false + + expect(err).to include("--ref can only be used with git sources") + end + + it "raises error when both --branch and --ref options are specified" do + bundle "plugin install foo --source https://gem.repo2 --branch main --ref v1.2.3", raise_on_error: false + + expect(out).not_to include("Installed plugin foo") + + expect(err).to include("You cannot specify `--branch` and `--ref` at the same time.") + end + + it "works with different load paths" do + build_repo2 do + build_plugin "testing" do |s| + s.write "plugins.rb", <<-RUBY + require "fubar" + class Test < Bundler::Plugin::API + command "check2" + + def exec(command, args) + puts "mate" + end + end + RUBY + s.require_paths = %w[lib src] + s.write("src/fubar.rb") + end + end + bundle "plugin install testing --source https://gem.repo2" + + bundle "check2", "no-color" => false + expect(out).to eq("mate") + end + + context "malformatted plugin" do + it "fails when plugins.rb is missing" do + update_repo2 do + build_plugin "foo", "1.1" + build_plugin "kung-foo", "1.1" + end + + bundle "plugin install foo kung-foo --version '1.0' --source https://gem.repo2" + + expect(out).to include("Installing foo 1.0") + expect(out).to include("Installing kung-foo 1.0") + plugin_should_be_installed("foo", "kung-foo") + + update_repo2 do + build_gem "charlie" + end + + bundle "plugin install charlie --source https://gem.repo2", raise_on_error: false + + expect(err).to include("Failed to install plugin `charlie`, due to Bundler::Plugin::MalformattedPlugin (plugins.rb was not found in the plugin.)") + + expect(global_plugin_gem("charlie-1.0")).not_to be_directory + + plugin_should_be_installed("foo", "kung-foo") + plugin_should_not_be_installed("charlie") + end + + it "fails when plugins.rb throws exception on load" do + build_repo2 do + build_plugin "chaplin" do |s| + s.write "plugins.rb", <<-RUBY + raise RuntimeError, "threw exception on load" + RUBY + end + end + + bundle "plugin install chaplin --source https://gem.repo2", raise_on_error: false + + expect(global_plugin_gem("chaplin-1.0")).not_to be_directory + + plugin_should_not_be_installed("chaplin") + end + end + + context "git plugins" do + it "installs form a git source" do + build_git "foo" do |s| + s.write "plugins.rb" + end + + bundle "plugin install foo --git #{lib_path("foo-1.0")}" + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + + it "installs form a local git source" do + build_git "foo" do |s| + s.write "plugins.rb" + end + + bundle "plugin install foo --git #{lib_path("foo-1.0")}" + + expect(out).to include("Installed plugin foo") + plugin_should_be_installed("foo") + end + end + + context "path plugins" do + it "installs from a path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + bundle "plugin install path_plugin --path #{lib_path("path_plugin-1.0")}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install path_plugin --path #{path}" + + expect(out).to include("Installed plugin path_plugin") + plugin_should_be_installed("path_plugin") + end + + it "installs from a relative path source when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + bundle "plugin install ga-plugin --path #{path}" + + plugin_should_be_installed("ga-plugin") + expect(local_plugin_gem("foo-1.0")).not_to be_directory + end + end + + context "Gemfile eval" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + end + + it "installs plugins listed in gemfile" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo' + gem 'myrack', "1.0.0" + G + + bundle "install" + + expect(out).to include("Installed plugin foo") + + expect(out).to include("Bundle complete!") + + expect(the_bundle).to include_gems("myrack 1.0.0") + plugin_should_be_installed("foo") + end + + it "overrides the index with the new plugin version" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "1.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + update_repo2 do + build_plugin "foo", "2.0.0" + end + + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "2.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + expected = local_plugin_gem("foo-2.0.0", "lib").to_s + expect(Bundler::Plugin.index.load_paths("foo")).to eq([expected]) + end + + it "respects bundler groups" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo' + gem 'myrack', "1.0.0" + G + + bundle "install", env: { "BUNDLE_WITHOUT" => "default" } + + expect(out).to include("Bundle complete! 1 Gemfile dependency, 0 gems now installed.") + end + + it "accepts plugin version" do + update_repo2 do + build_plugin "foo", "1.1.0" + end + + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "1.0" + G + + bundle "install" + + expect(out).to include("Installing foo 1.0") + + plugin_should_be_installed("foo") + + expect(out).to include("Bundle complete!") + end + + it "accepts git sources" do + build_git "ga-plugin" do |s| + s.write "plugins.rb" + end + + install_gemfile <<-G + source "https://gem.repo1" + plugin 'ga-plugin', :git => "#{lib_path("ga-plugin-1.0")}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + + it "accepts path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + install_gemfile <<-G + source "https://gem.repo1" + plugin 'ga-plugin', :path => "#{lib_path("ga-plugin-1.0")}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + + it "accepts relative path sources" do + build_lib "ga-plugin" do |s| + s.write "plugins.rb" + end + + path = lib_path("ga-plugin-1.0").relative_path_from(bundled_app) + install_gemfile <<-G + source "https://gem.repo1" + plugin 'ga-plugin', :path => "#{path}" + G + + expect(out).to include("Installed plugin ga-plugin") + plugin_should_be_installed("ga-plugin") + end + + context "in deployment mode" do + it "installs plugins" do + install_gemfile <<-G + source 'https://gem.repo2' + gem 'myrack', "1.0.0" + G + + bundle_config "deployment true" + install_gemfile <<-G + source 'https://gem.repo2' + plugin 'foo' + gem 'myrack', "1.0.0" + G + + expect(out).to include("Installed plugin foo") + + expect(out).to include("Bundle complete!") + + expect(the_bundle).to include_gems("myrack 1.0.0") + plugin_should_be_installed("foo") + end + end + end + + context "inline gemfiles" do + it "installs the listed plugins" do + code = <<-RUBY + require "bundler/inline" + + gemfile do + source 'https://gem.repo2' + plugin 'foo' + end + RUBY + + ruby code, artifice: "compact_index", env: { "BUNDLER_VERSION" => Bundler::VERSION } + expect(local_plugin_gem("foo-1.0", "plugins.rb")).to exist + end + end + + describe "local plugin" do + it "is installed when inside an app" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + gemfile "" + bundle "plugin install foo --source https://gem.repo2" + + plugin_should_be_installed("foo") + expect(local_plugin_gem("foo-1.0")).to be_directory + end + + context "conflict with global plugin" do + before do + update_repo2 do + build_plugin "fubar" do |s| + s.write "plugins.rb", <<-RUBY + class Fubar < Bundler::Plugin::API + command "shout" + + def exec(command, args) + puts "local_one" + end + end + RUBY + end + end + + # inside the app + gemfile "source 'https://gem.repo2'\nplugin 'fubar'" + bundle "install" + + update_repo2 do + build_plugin "fubar", "1.1" do |s| + s.write "plugins.rb", <<-RUBY + class Fubar < Bundler::Plugin::API + command "shout" + + def exec(command, args) + puts "global_one" + end + end + RUBY + end + end + + # outside the app + bundle "plugin install fubar --source https://gem.repo2", dir: tmp + end + + it "inside the app takes precedence over global plugin" do + bundle "shout" + expect(out).to eq("local_one") + end + + it "outside the app global plugin is used" do + bundle "shout", dir: tmp + expect(out).to eq("global_one") + end + end + end +end diff --git a/spec/bundler/plugins/list_spec.rb b/spec/bundler/plugins/list_spec.rb new file mode 100644 index 0000000000..30e3f82467 --- /dev/null +++ b/spec/bundler/plugins/list_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +RSpec.describe "bundler plugin list" do + before do + build_repo2 do + build_plugin "foo" do |s| + s.write "plugins.rb", <<-RUBY + class Foo < Bundler::Plugin::API + command "shout" + + def exec(command, args) + puts "Foo shout" + end + end + RUBY + end + build_plugin "bar" do |s| + s.write "plugins.rb", <<-RUBY + class Bar < Bundler::Plugin::API + command "scream" + + def exec(command, args) + puts "Bar scream" + end + end + RUBY + end + end + end + + context "no plugins installed" do + it "shows proper no plugins installed message" do + bundle "plugin list" + + expect(out).to include("No plugins installed") + end + end + + context "single plugin installed" do + it "shows plugin name with commands list" do + bundle "plugin install foo --source https://gem.repo2" + plugin_should_be_installed("foo") + bundle "plugin list" + + expected_output = "foo\n-----\n shout" + expect(out).to include(expected_output) + end + end + + context "multiple plugins installed" do + it "shows plugin names with commands list" do + bundle "plugin install foo bar --source https://gem.repo2" + plugin_should_be_installed("foo", "bar") + bundle "plugin list" + + expected_output = "foo\n-----\n shout\n\nbar\n-----\n scream" + expect(out).to include(expected_output) + end + end +end diff --git a/spec/bundler/plugins/source/example_spec.rb b/spec/bundler/plugins/source/example_spec.rb new file mode 100644 index 0000000000..4cd4a1a931 --- /dev/null +++ b/spec/bundler/plugins/source/example_spec.rb @@ -0,0 +1,456 @@ +# frozen_string_literal: true + +RSpec.describe "real source plugins" do + context "with a minimal source plugin" do + before do + build_repo2 do + build_plugin "bundler-source-mpath" do |s| + s.write "plugins.rb", <<-RUBY + require "bundler-source-mpath" + + class MPath < Bundler::Plugin::API + source "mpath" + + attr_reader :path + + def initialize(opts) + super + + @path = Pathname.new options["uri"] + end + + def fetch_gemspec_files + @spec_files ||= begin + glob = "{,*,*/*}.gemspec" + if installed? + search_path = install_path + else + search_path = path + end + Dir["\#{search_path.to_s}/\#{glob}"] + end + end + + def install(spec, opts) + mkdir_p(install_path.parent) + require 'fileutils' + FileUtils.cp_r(path, install_path) + + spec_path = install_path.join("\#{spec.full_name}.gemspec") + spec_path.open("wb") {|f| f.write spec.to_ruby } + spec.loaded_from = spec_path.to_s + + post_install(spec) + + nil + end + end + RUBY + end # build_plugin + end + + build_lib "a-path-gem" + + gemfile <<-G + source "https://gem.repo2" # plugin source + source "#{lib_path("a-path-gem-1.0")}", :type => :mpath do + gem "a-path-gem" + end + G + end + + it "installs" do + bundle "install" + + expect(out).to include("Bundle complete!") + + expect(the_bundle).to include_gems("a-path-gem 1.0") + end + + it "writes to lockfile" do + bundle "install" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "a-path-gem", "1.0" + end + + expect(lockfile).to eq <<~G + PLUGIN SOURCE + remote: #{lib_path("a-path-gem-1.0")} + type: mpath + specs: + a-path-gem (1.0) + + GEM + remote: https://gem.repo2/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + a-path-gem! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "provides correct #full_gem_path" do + bundle "install" + run <<-RUBY + puts Bundler.rubygems.find_name('a-path-gem').first.full_gem_path + RUBY + expect(out).to eq(bundle("info a-path-gem --path")) + end + + it "installs the gem executables" do + build_lib "gem_with_bin" do |s| + s.executables = ["foo"] + end + + install_gemfile <<-G + source "https://gem.repo2" # plugin source + source "#{lib_path("gem_with_bin-1.0")}", :type => :mpath do + gem "gem_with_bin" + end + G + + bundle "exec foo" + expect(out).to eq("1.0") + end + + describe "bundle cache/package" do + let(:uri_hash) { Digest(:SHA1).hexdigest(lib_path("a-path-gem-1.0").to_s) } + it "copies repository to vendor cache and uses it" do + bundle "install" + bundle :cache + + expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist + expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.git")).not_to exist + expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}/.bundlecache")).to be_file + + FileUtils.rm_r lib_path("a-path-gem-1.0") + expect(the_bundle).to include_gems("a-path-gem 1.0") + end + + it "copies repository to vendor cache and uses it even when installed with `path` configured" do + bundle_config "path vendor/bundle" + bundle :install + bundle :cache + + expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist + + FileUtils.rm_r lib_path("a-path-gem-1.0") + expect(the_bundle).to include_gems("a-path-gem 1.0") + end + + it "bundler package copies repository to vendor cache" do + bundle_config "path vendor/bundle" + bundle :install + bundle :cache + + expect(bundled_app("vendor/cache/a-path-gem-1.0-#{uri_hash}")).to exist + + FileUtils.rm_r lib_path("a-path-gem-1.0") + expect(the_bundle).to include_gems("a-path-gem 1.0") + end + end + + context "with lockfile" do + before do + lockfile <<-G + PLUGIN SOURCE + remote: #{lib_path("a-path-gem-1.0")} + type: mpath + specs: + a-path-gem (1.0) + + GEM + remote: https://gem.repo2/ + specs: + + PLATFORMS + #{generic_local_platform} + + DEPENDENCIES + a-path-gem! + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "installs" do + bundle "install" + + expect(the_bundle).to include_gems("a-path-gem 1.0") + end + end + end + + context "with a more elaborate source plugin" do + before do + build_repo2 do + build_plugin "bundler-source-gitp" do |s| + s.write "plugins.rb", <<-RUBY + require "open3" + + class SPlugin < Bundler::Plugin::API + source "gitp" + + attr_reader :ref + + def initialize(opts) + super + + @ref = options["ref"] || options["branch"] || options["tag"] || "main" + @unlocked = false + end + + def eql?(other) + other.is_a?(self.class) && uri == other.uri && ref == other.ref + end + + alias_method :==, :eql? + + def fetch_gemspec_files + @spec_files ||= begin + glob = "{,*,*/*}.gemspec" + if !cached? + cache_repo + end + + if installed? && !@unlocked + path = install_path + else + path = cache_path + end + + Dir["\#{path}/\#{glob}"] + end + end + + def install(spec, opts) + mkdir_p(install_path.dirname) + rm_rf(install_path) + `git clone --no-checkout --quiet "\#{cache_path}" "\#{install_path}"` + Open3.capture2e("git reset --hard \#{revision}", :chdir => install_path) + + spec_path = install_path.join("\#{spec.full_name}.gemspec") + spec_path.open("wb") {|f| f.write spec.to_ruby } + spec.loaded_from = spec_path.to_s + + post_install(spec) + + nil + end + + def options_to_lock + opts = {"revision" => revision} + opts["ref"] = ref if ref != "main" + opts + end + + def unlock! + @unlocked = true + @revision = latest_revision + end + + def app_cache_dirname + "\#{base_name}-\#{shortref_for_path(revision)}" + end + + private + + def cache_path + @cache_path ||= cache_dir.join("gitp", base_name) + end + + def cache_repo + `git clone --quiet \#{@options["uri"]} \#{cache_path}` + end + + def cached? + File.directory?(cache_path) + end + + def locked_revision + options["revision"] + end + + def revision + @revision ||= locked_revision || latest_revision + end + + def latest_revision + if !cached? || @unlocked + rm_rf(cache_path) + cache_repo + end + + output, _status = Open3.capture2e("git rev-parse --verify \#{@ref}", :chdir => cache_path) + output.strip + end + + def base_name + File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*}, ""), ".git") + end + + def shortref_for_path(ref) + ref[0..11] + end + + def install_path + @install_path ||= begin + git_scope = "\#{base_name}-\#{shortref_for_path(revision)}" + + gem_install_dir.join(git_scope) + end + end + + def installed? + File.directory?(install_path) + end + end + RUBY + end + end + + build_git "ma-gitp-gem" + + gemfile <<-G + source "https://gem.repo2" # plugin source + source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do + gem "ma-gitp-gem" + end + G + end + + it "handles the source option" do + bundle "install" + expect(out).to include("Bundle complete!") + expect(the_bundle).to include_gems("ma-gitp-gem 1.0") + end + + it "writes to lockfile" do + revision = revision_for(lib_path("ma-gitp-gem-1.0")) + bundle "install" + + checksums = checksums_section_when_enabled do |c| + c.no_checksum "ma-gitp-gem", "1.0" + end + + expect(lockfile).to eq <<~G + PLUGIN SOURCE + remote: #{lib_path("ma-gitp-gem-1.0")} + type: gitp + revision: #{revision} + specs: + ma-gitp-gem (1.0) + + GEM + remote: https://gem.repo2/ + specs: + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ma-gitp-gem! + #{checksums} + BUNDLED WITH + #{Bundler::VERSION} + G + end + + context "with lockfile" do + before do + revision = revision_for(lib_path("ma-gitp-gem-1.0")) + lockfile <<-G + PLUGIN SOURCE + remote: #{lib_path("ma-gitp-gem-1.0")} + type: gitp + revision: #{revision} + specs: + ma-gitp-gem (1.0) + + GEM + remote: https://gem.repo2/ + specs: + + PLATFORMS + #{generic_local_platform} + + DEPENDENCIES + ma-gitp-gem! + + BUNDLED WITH + #{Bundler::VERSION} + G + end + + it "installs" do + bundle "install" + expect(the_bundle).to include_gems("ma-gitp-gem 1.0") + end + + it "uses the locked ref" do + update_git "ma-gitp-gem" + bundle "install" + + run <<-RUBY + require 'ma/gitp/gem' + puts "WIN" unless defined?(MAGITPGEM_PREV_REF) + RUBY + expect(out).to eq("WIN") + end + + it "updates the deps on bundler update" do + update_git "ma-gitp-gem" + bundle "update ma-gitp-gem" + + run <<-RUBY + require 'ma/gitp/gem' + puts "WIN" if defined?(MAGITPGEM_PREV_REF) + RUBY + expect(out).to eq("WIN") + end + + it "updates the deps on change in gemfile" do + update_git "ma-gitp-gem", "1.1", path: lib_path("ma-gitp-gem-1.0"), gemspec: true + gemfile <<-G + source "https://gem.repo2" # plugin source + source "#{lib_path("ma-gitp-gem-1.0")}", :type => :gitp do + gem "ma-gitp-gem", "1.1" + end + G + bundle "install" + + expect(the_bundle).to include_gems("ma-gitp-gem 1.1") + end + end + + describe "bundle cache with gitp" do + it "copies repository to vendor cache and uses it" do + git = build_git "foo" + ref = git.ref_for("main", 11) + + install_gemfile <<-G + source "https://gem.repo2" # plugin source + source '#{lib_path("foo-1.0")}', :type => :gitp do + gem "foo" + end + G + + bundle :cache + expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist + expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.git")).not_to exist + expect(bundled_app("vendor/cache/foo-1.0-#{ref}/.bundlecache")).to be_file + + FileUtils.rm_r lib_path("foo-1.0") + expect(the_bundle).to include_gems "foo 1.0" + end + end + end +end diff --git a/spec/bundler/plugins/source_spec.rb b/spec/bundler/plugins/source_spec.rb new file mode 100644 index 0000000000..995e50e653 --- /dev/null +++ b/spec/bundler/plugins/source_spec.rb @@ -0,0 +1,111 @@ +# frozen_string_literal: true + +RSpec.describe "bundler source plugin" do + describe "plugins dsl eval for #source with :type option" do + before do + update_repo2 do + build_plugin "bundler-source-psource" do |s| + s.write "plugins.rb", <<-RUBY + class OPSource < Bundler::Plugin::API + source "psource" + end + RUBY + end + end + end + + it "installs bundler-source-* gem when no handler for source is present" do + install_gemfile <<-G + source "https://gem.repo2" + source "#{lib_path("gitp")}", :type => :psource do + end + G + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + plugin_should_be_installed("bundler-source-psource") + end + + it "enables the plugin to require a lib path" do + update_repo2 do + build_plugin "bundler-source-psource" do |s| + s.write "plugins.rb", <<-RUBY + require "bundler-source-psource" + class PSource < Bundler::Plugin::API + source "psource" + end + RUBY + end + end + + install_gemfile <<-G + source "https://gem.repo2" + source "#{lib_path("gitp")}", :type => :psource do + end + G + + expect(out).to include("Bundle complete!") + end + + context "with an explicit handler" do + before do + update_repo2 do + build_plugin "another-psource" do |s| + s.write "plugins.rb", <<-RUBY + class Cheater < Bundler::Plugin::API + source "psource" + end + RUBY + end + end + end + + context "explicit presence in gemfile" do + before do + install_gemfile <<-G + source "https://gem.repo2" + + plugin "another-psource" + + source "#{lib_path("gitp")}", :type => :psource do + end + G + end + + it "completes successfully" do + expect(out).to include("Bundle complete!") + end + + it "installs the explicit one" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + plugin_should_be_installed("another-psource") + end + + it "doesn't install the default one" do + plugin_should_not_be_installed("bundler-source-psource") + end + end + + context "explicit default source" do + before do + install_gemfile <<-G + source "https://gem.repo2" + + plugin "bundler-source-psource" + + source "#{lib_path("gitp")}", :type => :psource do + end + G + end + + it "completes successfully" do + expect(out).to include("Bundle complete!") + end + + it "installs the default one" do + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + plugin_should_be_installed("bundler-source-psource") + end + end + end + end +end diff --git a/spec/bundler/plugins/uninstall_spec.rb b/spec/bundler/plugins/uninstall_spec.rb new file mode 100644 index 0000000000..dedcc9f37c --- /dev/null +++ b/spec/bundler/plugins/uninstall_spec.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true + +RSpec.describe "bundler plugin uninstall" do + before do + build_repo2 do + build_plugin "foo" + build_plugin "kung-foo" + end + end + + it "shows proper error message when plugins are not specified" do + bundle "plugin uninstall" + expect(err).to include("No plugins to uninstall") + end + + it "uninstalls specified plugins" do + bundle "plugin install foo kung-foo --source https://gem.repo2" + plugin_should_be_installed("foo") + plugin_should_be_installed("kung-foo") + + bundle "plugin uninstall foo" + expect(out).to include("Uninstalled plugin foo") + plugin_should_not_be_installed("foo") + plugin_should_be_installed("kung-foo") + end + + it "shows proper message when plugin is not installed" do + bundle "plugin uninstall foo" + expect(err).to include("Plugin foo is not installed") + plugin_should_not_be_installed("foo") + end + + it "doesn't wipe out path plugins" do + build_lib "path_plugin" do |s| + s.write "plugins.rb" + end + path = lib_path("path_plugin-1.0") + expect(path).to be_a_directory + + allow(Bundler::SharedHelpers).to receive(:find_gemfile).and_return(bundled_app_gemfile) + + install_gemfile <<-G + source 'https://gem.repo2' + plugin 'path_plugin', :path => "#{path}" + gem 'myrack', '1.0.0' + G + + plugin_should_be_installed("path_plugin") + expect(Bundler::Plugin.index.plugin_path("path_plugin")).to eq path + + bundle "plugin uninstall path_plugin" + expect(out).to include("Uninstalled plugin path_plugin") + plugin_should_not_be_installed("path_plugin") + # the actual gem still exists though + expect(path).to be_a_directory + end + + describe "with --all" do + it "uninstalls all installed plugins" do + bundle "plugin install foo kung-foo --source https://gem.repo2" + plugin_should_be_installed("foo") + plugin_should_be_installed("kung-foo") + + bundle "plugin uninstall --all" + plugin_should_not_be_installed("foo") + plugin_should_not_be_installed("kung-foo") + end + + it "shows proper no plugins installed message when no plugins installed" do + bundle "plugin uninstall --all" + expect(out).to include("No plugins installed") + end + end +end |
