Skip to content

Commit a736178

Browse files
authored
Pluginmanager install preserve (elastic#17267)
* tests: integration tests for pluginmanager install --preserve * fix regression where pluginmanager's install --preserve flag didn't
1 parent b993bec commit a736178

File tree

3 files changed

+98
-8
lines changed

3 files changed

+98
-8
lines changed

lib/pluginmanager/install.rb

+3-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ def install_gems_list!(install_list)
214214
plugin_gem = gemfile.find(plugin)
215215
if preserve?
216216
puts("Preserving Gemfile gem options for plugin #{plugin}") if plugin_gem && !plugin_gem.options.empty?
217-
gemfile.update(plugin, version, options)
217+
# if the plugin exists and no version was specified, keep the existing requirements
218+
requirements = (plugin_gem && version.nil? ? plugin_gem.requirements : [version]).compact
219+
gemfile.update(plugin, *requirements, options)
218220
else
219221
gemfile.overwrite(plugin, version, options)
220222
end

qa/integration/specs/cli/install_spec.rb

+62-7
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,11 @@ def plugin_filename_re(name, version)
165165

166166
context "rubygems hosted plugin" do
167167
include_context "pluginmanager validation helpers"
168-
shared_examples("overwriting existing") do
168+
shared_context("install over existing") do
169169
before(:each) do
170170
aggregate_failures("precheck") do
171171
expect("#{plugin_name}-#{existing_plugin_version}").to_not be_installed_gem
172-
expect("#{plugin_name}-#{specified_plugin_version}").to_not be_installed_gem
172+
expect("#{plugin_name}").to_not be_installed_gem
173173
end
174174
aggregate_failures("setup") do
175175
execute = @logstash_plugin.install(plugin_name, version: existing_plugin_version)
@@ -178,9 +178,12 @@ def plugin_filename_re(name, version)
178178
expect(execute.exit_code).to eq(0)
179179

180180
expect("#{plugin_name}-#{existing_plugin_version}").to be_installed_gem
181-
expect("#{plugin_name}-#{specified_plugin_version}").to_not be_installed_gem
181+
expect(plugin_name).to be_in_gemfile.with_requirements(existing_plugin_version)
182182
end
183183
end
184+
end
185+
shared_examples("overwriting existing with explicit version") do
186+
include_context "install over existing"
184187
it "installs the specified version and removes the pre-existing one" do
185188
execute = @logstash_plugin.install(plugin_name, version: specified_plugin_version)
186189

@@ -197,20 +200,72 @@ def plugin_filename_re(name, version)
197200
end
198201
end
199202

200-
context "when installing over an older version" do
203+
context "when installing over an older version using --version" do
201204
let(:plugin_name) { "logstash-filter-qatest" }
202205
let(:existing_plugin_version) { "0.1.0" }
203206
let(:specified_plugin_version) { "0.1.1" }
204207

205-
include_examples "overwriting existing"
208+
include_examples "overwriting existing with explicit version"
206209
end
207210

208-
context "when installing over a newer version" do
211+
context "when installing over a newer version using --version" do
209212
let(:plugin_name) { "logstash-filter-qatest" }
210213
let(:existing_plugin_version) { "0.1.0" }
211214
let(:specified_plugin_version) { "0.1.1" }
212215

213-
include_examples "overwriting existing"
216+
include_examples "overwriting existing with explicit version"
217+
end
218+
219+
context "when installing over existing without --version" do
220+
let(:plugin_name) { "logstash-filter-qatest" }
221+
let(:existing_plugin_version) { "0.1.0" }
222+
223+
include_context "install over existing"
224+
225+
context "with --preserve" do
226+
it "succeeds without changing the requirements in the Gemfile" do
227+
execute = @logstash_plugin.install(plugin_name, preserve: true)
228+
229+
aggregate_failures("command execution") do
230+
expect(execute.stderr_and_stdout).to match(INSTALLATION_SUCCESS_RE)
231+
expect(execute.exit_code).to eq(0)
232+
end
233+
234+
installed = @logstash_plugin.list(verbose: true)
235+
expect(installed.stderr_and_stdout).to match(/#{Regexp.escape plugin_name}/)
236+
237+
# we want to ensure that the act of installing an already-installed plugin
238+
# does not change its requirements in the gemfile, and leaves the previously-installed
239+
# version in-tact.
240+
expect(plugin_name).to be_in_gemfile.with_requirements(existing_plugin_version)
241+
expect("#{plugin_name}-#{existing_plugin_version}").to be_installed_gem
242+
end
243+
end
244+
245+
context "without --preserve" do
246+
# this spec is OBSERVED behaviour, which I believe to be undesirable.
247+
it "succeeds and removes the version requirement from the Gemfile" do
248+
execute = @logstash_plugin.install(plugin_name)
249+
250+
aggregate_failures("command execution") do
251+
expect(execute.stderr_and_stdout).to match(INSTALLATION_SUCCESS_RE)
252+
expect(execute.exit_code).to eq(0)
253+
end
254+
255+
installed = @logstash_plugin.list(plugin_name, verbose: true)
256+
expect(installed.stderr_and_stdout).to match(/#{Regexp.escape plugin_name}/)
257+
258+
# This is the potentially-undesirable surprising behaviour, specified here
259+
# as a means of documentation, not a promise of future behavior.
260+
expect(plugin_name).to be_in_gemfile.without_requirements
261+
262+
# we expect _a_ version of the plugin to be installed, but cannot be opinionated
263+
# about which version was installed because bundler won't necessarily re-resolve
264+
# the dependency graph to get us an upgrade since the no-requirements dependency
265+
# is still met (but it MAY do so if also installing plugins that are not present).
266+
expect("#{plugin_name}").to be_installed_gem
267+
end
268+
end
214269
end
215270

216271
context "installing plugin that isn't present" do

qa/integration/specs/cli/pluginmanager_spec_helper.rb

+33
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'pathname'
2+
require 'bundler/definition'
23

34
shared_context "pluginmanager validation helpers" do
45

@@ -40,12 +41,44 @@
4041
end
4142
end
4243

44+
matcher :be_in_gemfile do
45+
match do |actual|
46+
common(actual)
47+
@dep && (@version_requirements.nil? || @version_requirements == @dep.requirement)
48+
end
49+
define_method :common do |actual|
50+
@definition = Bundler::Definition.build(logstash_gemfile, logstash_gemfile_lock, false)
51+
@dep = @definition.dependencies.find { |s| s.name == actual }
52+
end
53+
chain :with_requirements do |version_requirements|
54+
@version_requirements = Gem::Requirement.create(version_requirements)
55+
end
56+
chain :without_requirements do
57+
@version_requirements = Gem::Requirement.default
58+
end
59+
failure_message do |actual|
60+
if @dep.nil?
61+
"expected the gem to be in the gemspec, but it wasn't (#{@definition.dependencies.map(&:name)})"
62+
else
63+
"expected the `#{actual}` gem to have requirements `#{@version_requirements}`, but they were `#{@dep.requirement}`"
64+
end
65+
end
66+
end
67+
4368
def logstash_home
4469
return super() if defined?(super)
4570
return @logstash.logstash_home if @logstash
4671
fail("no @logstash, so we can't get logstash_home")
4772
end
4873

74+
def logstash_gemfile
75+
Pathname.new(logstash_home) / "Gemfile"
76+
end
77+
78+
def logstash_gemfile_lock
79+
Pathname.new(logstash_home) / "Gemfile.lock"
80+
end
81+
4982
def logstash_gemdir
5083
pathname_base = (Pathname.new(logstash_home) / "vendor" / "bundle" / "jruby")
5184
candidate_dirs = pathname_base.glob("[0-9]*")

0 commit comments

Comments
 (0)