r10k 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +4 -1
  3. data/.github/workflows/release.yml +3 -2
  4. data/.github/workflows/rspec_tests.yml +1 -1
  5. data/.travis.yml +8 -1
  6. data/CHANGELOG.mkd +10 -0
  7. data/CODEOWNERS +1 -1
  8. data/doc/common-patterns.mkd +1 -0
  9. data/doc/dynamic-environments/configuration.mkd +90 -43
  10. data/doc/dynamic-environments/usage.mkd +7 -7
  11. data/doc/puppetfile.mkd +23 -3
  12. data/docker/Gemfile +1 -1
  13. data/docker/Makefile +4 -3
  14. data/docker/docker-compose.yml +18 -0
  15. data/docker/r10k/Dockerfile +1 -1
  16. data/docker/r10k/docker-entrypoint.sh +0 -1
  17. data/docker/r10k/release.Dockerfile +1 -1
  18. data/docker/spec/dockerfile_spec.rb +26 -32
  19. data/integration/tests/git_source/git_source_repeated_remote.rb +2 -2
  20. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module.rb +2 -1
  21. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module_static.rb +2 -1
  22. data/integration/tests/user_scenario/basic_workflow/multi_source_custom_forge_git_module.rb +1 -1
  23. data/integration/tests/user_scenario/basic_workflow/single_env_custom_forge_git_module.rb +2 -1
  24. data/lib/r10k/action/deploy/display.rb +9 -3
  25. data/lib/r10k/action/deploy/environment.rb +36 -14
  26. data/lib/r10k/cli/deploy.rb +5 -3
  27. data/lib/r10k/environment/base.rb +1 -1
  28. data/lib/r10k/environment/git.rb +17 -2
  29. data/lib/r10k/environment/name.rb +22 -4
  30. data/lib/r10k/environment/svn.rb +11 -2
  31. data/lib/r10k/environment/with_modules.rb +1 -1
  32. data/lib/r10k/git/rugged/credentials.rb +22 -15
  33. data/lib/r10k/module/forge.rb +15 -3
  34. data/lib/r10k/module/git.rb +24 -23
  35. data/lib/r10k/module/local.rb +1 -1
  36. data/lib/r10k/module/svn.rb +14 -11
  37. data/lib/r10k/settings.rb +6 -1
  38. data/lib/r10k/source/base.rb +5 -0
  39. data/lib/r10k/source/git.rb +4 -1
  40. data/lib/r10k/source/hash.rb +4 -2
  41. data/lib/r10k/source/svn.rb +5 -1
  42. data/lib/r10k/util/setopts.rb +33 -12
  43. data/lib/r10k/version.rb +1 -1
  44. data/locales/r10k.pot +22 -18
  45. data/r10k.gemspec +1 -1
  46. data/spec/unit/action/deploy/display_spec.rb +4 -0
  47. data/spec/unit/action/deploy/environment_spec.rb +111 -10
  48. data/spec/unit/environment/git_spec.rb +16 -0
  49. data/spec/unit/environment/name_spec.rb +28 -0
  50. data/spec/unit/environment/svn_spec.rb +12 -0
  51. data/spec/unit/git/rugged/credentials_spec.rb +10 -0
  52. data/spec/unit/module/forge_spec.rb +6 -0
  53. data/spec/unit/module/git_spec.rb +1 -1
  54. data/spec/unit/module_spec.rb +59 -9
  55. data/spec/unit/util/setopts_spec.rb +25 -1
  56. metadata +5 -11
  57. data/azure-pipelines.yml +0 -87
data/r10k.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.license = 'Apache-2.0'
24
24
 
25
25
  s.add_dependency 'colored2', '3.1.2'
26
- s.add_dependency 'cri', ['>= 2.15.10', '< 3.0.0']
26
+ s.add_dependency 'cri', '2.15.10'
27
27
 
28
28
  s.add_dependency 'log4r', '1.1.10'
29
29
  s.add_dependency 'multi_json', '~> 1.10'
@@ -8,6 +8,10 @@ describe R10K::Action::Deploy::Display do
8
8
  described_class.new({puppetfile: true}, [])
9
9
  end
10
10
 
11
+ it "accepts a modules option" do
12
+ described_class.new({modules: true}, [])
13
+ end
14
+
11
15
  it "accepts a detail option" do
12
16
  described_class.new({detail: true}, [])
13
17
  end
@@ -19,6 +19,10 @@ describe R10K::Action::Deploy::Environment do
19
19
  described_class.new({puppetfile: true}, [])
20
20
  end
21
21
 
22
+ it "can accept a modules option" do
23
+ described_class.new({modules: true}, [])
24
+ end
25
+
22
26
  it "can accept a default_branch_override option" do
23
27
  described_class.new({:'default-branch-override' => 'default_branch_override_name'}, [])
24
28
  end
@@ -42,6 +46,17 @@ describe R10K::Action::Deploy::Environment do
42
46
  it 'can accept a token option' do
43
47
  described_class.new({ 'oauth-token': '/nonexistent' }, [])
44
48
  end
49
+
50
+ describe "initializing errors" do
51
+ let (:settings) { { deploy: { purge_levels: [:environment],
52
+ purge_whitelist: ['coolfile', 'coolfile2'],
53
+ purge_allowlist: ['anothercoolfile']}}}
54
+
55
+ subject { described_class.new({config: "/some/nonexistent/path"}, [], settings)}
56
+ it 'errors out when both purge_whitelist and purge_allowlist are set' do
57
+ expect{subject}.to raise_error(R10K::Error, /Values found for both purge_whitelist and purge_allowlist./)
58
+ end
59
+ end
45
60
  end
46
61
 
47
62
  describe "when called" do
@@ -58,6 +73,29 @@ describe R10K::Action::Deploy::Environment do
58
73
  )
59
74
  end
60
75
 
76
+ describe "with puppetfile or modules flag" do
77
+ let(:deployment) { R10K::Deployment.new(mock_config) }
78
+ let(:puppetfile) { instance_double("R10K::Puppetfile", modules: []).as_null_object }
79
+
80
+ before do
81
+ expect(R10K::Deployment).to receive(:new).and_return(deployment)
82
+ expect(R10K::Puppetfile).to receive(:new).and_return(puppetfile).at_least(:once)
83
+ end
84
+
85
+ it "syncs the puppetfile when given the puppetfile flag" do
86
+ expect(puppetfile).to receive(:accept).and_return([])
87
+ action = described_class.new({config: "/some/nonexistent/path", puppetfile: true}, [])
88
+ action.call
89
+ end
90
+
91
+ it "syncs the puppetfile when given the modules flag" do
92
+ expect(puppetfile).to receive(:accept).and_return([])
93
+ action = described_class.new({config: "/some/nonexistent/path", modules: true}, [])
94
+ action.call
95
+ end
96
+
97
+ end
98
+
61
99
  describe "with an environment that doesn't exist" do
62
100
  let(:deployment) do
63
101
  R10K::Deployment.new(mock_config)
@@ -77,7 +115,7 @@ describe R10K::Action::Deploy::Environment do
77
115
  end
78
116
 
79
117
  describe "with no-force" do
80
- subject { described_class.new({ config: "/some/nonexistent/path", puppetfile: true, :'no-force' => true}, %w[first]) }
118
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true, :'no-force' => true}, %w[first]) }
81
119
 
82
120
  it "tries to preserve local modifications" do
83
121
  expect(subject.force).to equal(false)
@@ -175,6 +213,37 @@ describe R10K::Action::Deploy::Environment do
175
213
 
176
214
  end
177
215
 
216
+ describe "Purging white/allowlist" do
217
+
218
+ let(:settings) { { deploy: { purge_levels: [:environment], purge_allowlist: ['coolfile', 'coolfile2'] } } }
219
+
220
+ let(:deployment) do
221
+ R10K::Deployment.new(mock_config.merge(settings))
222
+ end
223
+
224
+ before do
225
+ expect(R10K::Deployment).to receive(:new).and_return(deployment)
226
+ end
227
+
228
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true }, %w[PREFIX_first], settings) }
229
+
230
+ it "reads in the purge_allowlist setting and purges accordingly" do
231
+ expect(subject.logger).to receive(:debug).with(/purging unmanaged content for environment/i)
232
+ expect(subject.instance_variable_get(:@user_purge_allowlist)).to eq(['coolfile', 'coolfile2'])
233
+ subject.call
234
+ end
235
+
236
+ describe "purge_whitelist" do
237
+ let (:settings) { { deploy: { purge_levels: [:environment], purge_whitelist: ['coolfile', 'coolfile2'] } } }
238
+
239
+ it "reads in the purge_whitelist setting and still sets it to purge_allowlist and purges accordingly" do
240
+ expect(subject.logger).to receive(:debug).with(/purging unmanaged content for environment/i)
241
+ expect(subject.instance_variable_get(:@user_purge_allowlist)).to eq(['coolfile', 'coolfile2'])
242
+ subject.call
243
+ end
244
+ end
245
+ end
246
+
178
247
  describe "purge_levels" do
179
248
  let(:settings) { { deploy: { purge_levels: purge_levels } } }
180
249
 
@@ -186,7 +255,7 @@ describe R10K::Action::Deploy::Environment do
186
255
  expect(R10K::Deployment).to receive(:new).and_return(deployment)
187
256
  end
188
257
 
189
- subject { described_class.new({ config: "/some/nonexistent/path", puppetfile: true }, %w[PREFIX_first], settings) }
258
+ subject { described_class.new({ config: "/some/nonexistent/path", modules: true }, %w[PREFIX_first], settings) }
190
259
 
191
260
  describe "deployment purge level" do
192
261
  let(:purge_levels) { [:deployment] }
@@ -262,7 +331,7 @@ describe R10K::Action::Deploy::Environment do
262
331
  described_class.new(
263
332
  {
264
333
  config: '/some/nonexistent/path',
265
- puppetfile: true,
334
+ modules: true,
266
335
  'generate-types': true
267
336
  },
268
337
  %w[first second]
@@ -316,7 +385,7 @@ describe R10K::Action::Deploy::Environment do
316
385
  described_class.new(
317
386
  {
318
387
  config: '/some/nonexistent/path',
319
- puppetfile: true,
388
+ modules: true,
320
389
  'generate-types': false
321
390
  },
322
391
  %w[first]
@@ -390,9 +459,25 @@ describe R10K::Action::Deploy::Environment do
390
459
 
391
460
  let(:mock_stateful_repo_1) { instance_double("R10K::Git::StatefulRepository", :head => "123456") }
392
461
  let(:mock_stateful_repo_2) { instance_double("R10K::Git::StatefulRepository", :head => "654321") }
393
- let(:mock_git_module_1) { instance_double("R10K::Module::Git", :name => "my_cool_module", :version => "1.0", :repo => mock_stateful_repo_1) }
394
- let(:mock_git_module_2) { instance_double("R10K::Module::Git", :name => "my_lame_module", :version => "0.0.1", :repo => mock_stateful_repo_2) }
395
- let(:mock_forge_module_1) { double(:name => "their_shiny_module", :version => "2.0.0") }
462
+ let(:mock_git_module_1) do
463
+ instance_double("R10K::Module::Git",
464
+ :name => "my_cool_module",
465
+ :properties => {
466
+ :type => :git,
467
+ :expected => "1.0",
468
+ :actual => mock_stateful_repo_1.head
469
+ })
470
+ end
471
+ let(:mock_git_module_2) do
472
+ instance_double("R10K::Module::Git",
473
+ :name => "my_uncool_module",
474
+ :properties => {
475
+ :type => :git,
476
+ :expected => "0.0.1",
477
+ :actual => mock_stateful_repo_2.head
478
+ })
479
+ end
480
+ let(:mock_forge_module_1) { double(:name => "their_shiny_module", :properties => { :expected => "2.0.0" }) }
396
481
  let(:mock_puppetfile) { instance_double("R10K::Puppetfile", :modules => [mock_git_module_1, mock_git_module_2, mock_forge_module_1]) }
397
482
 
398
483
  before(:all) do
@@ -405,9 +490,8 @@ describe R10K::Action::Deploy::Environment do
405
490
  Dir.delete(@tmp_path)
406
491
  end
407
492
 
408
- it "writes the .r10k-deploy file correctly" do
493
+ it "writes the .r10k-deploy file correctly if all goes well" do
409
494
  allow(R10K::Puppetfile).to receive(:new).and_return(mock_puppetfile)
410
- allow(mock_forge_module_1).to receive(:repo).and_raise(NoMethodError)
411
495
 
412
496
  fake_env = Fake_Environment.new(@tmp_path, {:name => "my_cool_environment", :signature => "pablo picasso"})
413
497
  allow(fake_env).to receive(:modules).and_return(mock_puppetfile.modules)
@@ -424,13 +508,30 @@ describe R10K::Action::Deploy::Environment do
424
508
  expect(r10k_deploy['module_deploys'][0]['name']).to eq("my_cool_module")
425
509
  expect(r10k_deploy['module_deploys'][0]['version']).to eq("1.0")
426
510
  expect(r10k_deploy['module_deploys'][0]['sha']).to eq("123456")
427
- expect(r10k_deploy['module_deploys'][1]['name']).to eq("my_lame_module")
511
+ expect(r10k_deploy['module_deploys'][1]['name']).to eq("my_uncool_module")
428
512
  expect(r10k_deploy['module_deploys'][1]['version']).to eq("0.0.1")
429
513
  expect(r10k_deploy['module_deploys'][1]['sha']).to eq("654321")
430
514
  expect(r10k_deploy['module_deploys'][2]['name']).to eq("their_shiny_module")
431
515
  expect(r10k_deploy['module_deploys'][2]['version']).to eq("2.0.0")
432
516
  expect(r10k_deploy['module_deploys'][2]['sha']).to eq(nil)
517
+ end
433
518
 
519
+ it "writes the .r10k-deploy file correctly if there's a failure" do
520
+ allow(R10K::Puppetfile).to receive(:new).and_return(mock_puppetfile)
521
+
522
+ fake_env = Fake_Environment.new(@tmp_path, {:name => "my_cool_environment", :signature => "pablo picasso"})
523
+ allow(fake_env).to receive(:modules).and_return(mock_puppetfile.modules)
524
+ allow(mock_forge_module_1).to receive(:properties).and_raise(StandardError)
525
+ subject.send(:write_environment_info!, fake_env, "2019-01-01 23:23:22 +0000", true)
526
+
527
+ file_contents = File.read("#{@tmp_path}/.r10k-deploy.json")
528
+ r10k_deploy = JSON.parse(file_contents)
529
+
530
+ expect(r10k_deploy['name']).to eq("my_cool_environment")
531
+ expect(r10k_deploy['signature']).to eq("pablo picasso")
532
+ expect(r10k_deploy['started_at']).to eq("2019-01-01 23:23:22 +0000")
533
+ expect(r10k_deploy['deploy_success']).to eq(true)
534
+ expect(r10k_deploy['module_deploys'].length).to eq(0)
434
535
  end
435
536
  end
436
537
  end
@@ -15,6 +15,22 @@ describe R10K::Environment::Git do
15
15
  )
16
16
  end
17
17
 
18
+ describe "initializing" do
19
+ subject do
20
+ described_class.new('name', '/dir', 'ref', {
21
+ :remote => 'url',
22
+ :ref => 'value',
23
+ :puppetfile_name => 'Puppetfile',
24
+ :moduledir => 'modules',
25
+ :modules => { },
26
+ })
27
+ end
28
+
29
+ it "accepts valid base class initialization arguments" do
30
+ expect(subject.name).to eq 'name'
31
+ end
32
+ end
33
+
18
34
  describe "storing attributes" do
19
35
  it "can return the environment name" do
20
36
  expect(subject.name).to eq 'myenv'
@@ -2,6 +2,34 @@ require 'spec_helper'
2
2
  require 'r10k/environment/name'
3
3
 
4
4
  describe R10K::Environment::Name do
5
+ describe "strip_component" do
6
+ it "does not modify the given name when no strip_component is given" do
7
+ bn = described_class.new('myenv', source: 'source', prefix: false)
8
+ expect(bn.dirname).to eq 'myenv'
9
+ end
10
+
11
+ it "removes the first occurance of a regex match when a regex is given" do
12
+ bn = described_class.new('myenv', source: 'source', prefix: false, strip_component: '/env/')
13
+ expect(bn.dirname).to eq 'my'
14
+ end
15
+
16
+ it "does not modify the given name when there is no regex match" do
17
+ bn = described_class.new('myenv', source: 'source', prefix: false, strip_component: '/bar/')
18
+ expect(bn.dirname).to eq 'myenv'
19
+ end
20
+
21
+ it "removes the given name's prefix when it matches strip_component" do
22
+ bn = described_class.new('env/prod', source: 'source', prefix: false, strip_component: 'env/')
23
+ expect(bn.dirname).to eq 'prod'
24
+ end
25
+
26
+ it "raises an error when given an integer" do
27
+ expect {
28
+ described_class.new('env/prod', source: 'source', prefix: false, strip_component: 4)
29
+ }.to raise_error(%r{Improper.*"4"})
30
+ end
31
+ end
32
+
5
33
  describe "prefixing" do
6
34
  it "uses the branch name as the dirname when prefixing is off" do
7
35
  bn = described_class.new('mybranch', :source => 'source', :prefix => false)
@@ -16,6 +16,18 @@ describe R10K::Environment::SVN do
16
16
 
17
17
  let(:working_dir) { subject.working_dir }
18
18
 
19
+ describe "initializing" do
20
+ subject do
21
+ described_class.new('name', '/dir', 'ref', {
22
+ :puppetfile_name => 'Puppetfile',
23
+ })
24
+ end
25
+
26
+ it "accepts valid base class initialization arguments" do
27
+ expect(subject.name).to eq 'name'
28
+ end
29
+ end
30
+
19
31
  describe "storing attributes" do
20
32
  it "can return the environment name" do
21
33
  expect(subject.name).to eq 'myenv'
@@ -143,6 +143,16 @@ describe R10K::Git::Rugged::Credentials, :unless => R10K::Util::Platform.jruby?
143
143
  expect(creds.instance_variable_get(:@password)).to eq("my_token")
144
144
  expect(creds.instance_variable_get(:@username)).to eq("x-oauth-token")
145
145
  end
146
+
147
+ it 'only reads the token in once' do
148
+ expect($stdin).to receive(:read).and_return("my_token").once
149
+ R10K::Git.settings[:oauth_token] = '-'
150
+ R10K::Git.settings[:repositories] = [{remote: "https://tessier-ashpool.freeside/repo.git"}]
151
+ creds = subject.get_plaintext_credentials("https://tessier-ashpool.freeside/repo.git", nil)
152
+ expect(creds.instance_variable_get(:@password)).to eq("my_token")
153
+ creds = subject.get_plaintext_credentials("https://tessier-ashpool.freeside/repo.git", nil)
154
+ expect(creds.instance_variable_get(:@password)).to eq("my_token")
155
+ end
146
156
  end
147
157
 
148
158
  describe "generating default credentials" do
@@ -23,6 +23,12 @@ describe R10K::Module::Forge do
23
23
  end
24
24
  end
25
25
 
26
+ describe "implementing the standard options interface" do
27
+ it "should implement {type: forge}" do
28
+ expect(described_class).to be_implement('branan-eight_hundred', {type: 'forge', version: '8.0.0', source: 'not implemented'})
29
+ end
30
+ end
31
+
26
32
  describe "setting attributes" do
27
33
  subject { described_class.new('branan/eight_hundred', '/moduledir', '8.0.0') }
28
34
 
@@ -123,7 +123,7 @@ describe R10K::Module::Git do
123
123
  let(:opts) { { unrecognized: true } }
124
124
 
125
125
  it "raises an error" do
126
- expect { test_module(opts) }.to raise_error(ArgumentError, /unhandled options.*unrecognized/i)
126
+ expect { test_module(opts) }.to raise_error(ArgumentError, /cannot handle option 'unrecognized'/)
127
127
  end
128
128
  end
129
129
 
@@ -3,20 +3,70 @@ require 'r10k/module'
3
3
 
4
4
  describe R10K::Module do
5
5
  describe 'delegating to R10K::Module::Git' do
6
- it "accepts args {:git => 'git url}" do
7
- obj = R10K::Module.new('foo', '/modulepath', :git => 'git url')
8
- expect(obj).to be_a_kind_of(R10K::Module::Git)
6
+ [ {git: 'git url'},
7
+ {type: 'git', source: 'git url'},
8
+ ].each do |scenario|
9
+ it "accepts a name matching 'test' and args #{scenario.inspect}" do
10
+ obj = R10K::Module.new('test', '/modulepath', scenario)
11
+ expect(obj).to be_a_kind_of(R10K::Module::Git)
12
+ expect(obj.send(:instance_variable_get, :'@remote')).to eq('git url')
13
+ end
14
+ end
15
+ end
16
+
17
+ describe 'delegating to R10K::Module::Svn' do
18
+ [ {svn: 'svn url'},
19
+ {type: 'svn', source: 'svn url'},
20
+ ].each do |scenario|
21
+ it "accepts a name matching 'test' and args #{scenario.inspect}" do
22
+ obj = R10K::Module.new('test', '/modulepath', scenario)
23
+ expect(obj).to be_a_kind_of(R10K::Module::SVN)
24
+ expect(obj.send(:instance_variable_get, :'@url')).to eq('svn url')
25
+ end
9
26
  end
10
27
  end
11
28
 
12
29
  describe 'delegating to R10K::Module::Forge' do
13
- [
14
- ['bar/quux', nil],
15
- ['bar-quux', nil],
16
- ['bar/quux', '8.0.0'],
30
+ [ 'bar/quux',
31
+ 'bar-quux',
17
32
  ].each do |scenario|
18
- it "accepts a name matching #{scenario[0]} and args #{scenario[1].inspect}" do
19
- expect(R10K::Module.new(scenario[0], '/modulepath', scenario[1])).to be_a_kind_of(R10K::Module::Forge)
33
+ it "accepts a name matching #{scenario} and args nil" do
34
+ obj = R10K::Module.new(scenario, '/modulepath', nil)
35
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
36
+ end
37
+ end
38
+ [ '8.0.0',
39
+ {type: 'forge', version: '8.0.0'},
40
+ ].each do |scenario|
41
+ it "accepts a name matching bar-quux and args #{scenario.inspect}" do
42
+ obj = R10K::Module.new('bar-quux', '/modulepath', scenario)
43
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
44
+ expect(obj.send(:instance_variable_get, :'@expected_version')).to eq('8.0.0')
45
+ end
46
+ end
47
+
48
+ describe 'when the module is ostensibly on disk' do
49
+ before do
50
+ owner = 'theowner'
51
+ module_name = 'themodulename'
52
+ @title = "#{owner}-#{module_name}"
53
+ metadata = <<~METADATA
54
+ {
55
+ "name": "#{@title}",
56
+ "version": "1.2.0"
57
+ }
58
+ METADATA
59
+ @dirname = Dir.mktmpdir
60
+ module_path = File.join(@dirname, module_name)
61
+ FileUtils.mkdir(module_path)
62
+ File.open("#{module_path}/metadata.json", 'w') do |file|
63
+ file.write(metadata)
64
+ end
65
+ end
66
+
67
+ it 'sets the expected version to what is found in the metadata' do
68
+ obj = R10K::Module.new(@title, @dirname, nil)
69
+ expect(obj.send(:instance_variable_get, :'@expected_version')).to eq('1.2.0')
20
70
  end
21
71
  end
22
72
  end
@@ -10,7 +10,10 @@ describe R10K::Util::Setopts do
10
10
 
11
11
  def initialize(opts = {})
12
12
  setopts(opts, {
13
- :valid => :self, :alsovalid => :self, :truthyvalid => true,
13
+ :valid => :self,
14
+ :duplicate => :valid,
15
+ :alsovalid => :self,
16
+ :truthyvalid => true,
14
17
  :validalias => :valid,
15
18
  :ignoreme => nil
16
19
  })
@@ -53,7 +56,28 @@ describe R10K::Util::Setopts do
53
56
  }.to raise_error(ArgumentError, /cannot handle option 'notvalid'/)
54
57
  end
55
58
 
59
+ it "warns when given an unhandled option and raise_on_unhandled=false" do
60
+ test = Class.new { include R10K::Util::Setopts }.new
61
+ allow(test).to receive(:logger).and_return(spy)
62
+
63
+ test.send(:setopts, {valid: :value, invalid: :value},
64
+ {valid: :self},
65
+ raise_on_unhandled: false)
66
+
67
+ expect(test.logger).to have_received(:warn).with(%r{cannot handle option 'invalid'})
68
+ end
69
+
56
70
  it "ignores values that are marked as unhandled" do
57
71
  klass.new(:ignoreme => "IGNORE ME!")
58
72
  end
73
+
74
+ it "warns when given conflicting options" do
75
+ test = Class.new { include R10K::Util::Setopts }.new
76
+ allow(test).to receive(:logger).and_return(spy)
77
+
78
+ test.send(:setopts, {valid: :one, duplicate: :two},
79
+ {valid: :arg, duplicate: :arg})
80
+
81
+ expect(test.logger).to have_received(:warn).with(%r{valid.*duplicate.*conflict.*not both})
82
+ end
59
83
  end