r10k 1.3.5 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (127) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +1 -1
  3. data/CHANGELOG.mkd +210 -0
  4. data/CONTRIBUTING.mkd +105 -0
  5. data/Gemfile +2 -6
  6. data/README.mkd +97 -0
  7. data/doc/common-patterns.mkd +44 -0
  8. data/doc/dynamic-environments.mkd +12 -5
  9. data/doc/dynamic-environments/configuration.mkd +16 -1
  10. data/doc/dynamic-environments/{git-environments.markdown → git-environments.mkd} +13 -9
  11. data/doc/dynamic-environments/introduction.mkd +1 -2
  12. data/doc/dynamic-environments/master-configuration.mkd +70 -0
  13. data/doc/dynamic-environments/quickstart.mkd +241 -0
  14. data/doc/dynamic-environments/svn-environments.mkd +45 -0
  15. data/doc/dynamic-environments/usage.mkd +44 -5
  16. data/doc/dynamic-environments/workflow-guide.mkd +247 -0
  17. data/doc/faq.mkd +52 -0
  18. data/doc/puppetfile.mkd +203 -0
  19. data/lib/r10k/action/cri_runner.rb +75 -0
  20. data/lib/r10k/action/deploy.rb +9 -0
  21. data/lib/r10k/action/deploy/display.rb +104 -0
  22. data/lib/r10k/action/deploy/environment.rb +92 -0
  23. data/lib/r10k/action/deploy/module.rb +70 -0
  24. data/lib/r10k/action/puppetfile.rb +10 -0
  25. data/lib/r10k/action/puppetfile/check.rb +41 -0
  26. data/lib/r10k/action/puppetfile/cri_runner.rb +32 -0
  27. data/lib/r10k/action/puppetfile/install.rb +53 -0
  28. data/lib/r10k/action/puppetfile/purge.rb +37 -0
  29. data/lib/r10k/action/runner.rb +36 -0
  30. data/lib/r10k/action/visitor.rb +31 -0
  31. data/lib/r10k/cli/deploy.rb +14 -45
  32. data/lib/r10k/cli/puppetfile.rb +15 -53
  33. data/lib/r10k/deployment.rb +113 -58
  34. data/lib/r10k/deployment/basedir.rb +3 -38
  35. data/lib/r10k/deployment/config.rb +2 -1
  36. data/lib/r10k/deployment/source.rb +2 -0
  37. data/lib/r10k/environment/base.rb +40 -0
  38. data/lib/r10k/environment/git.rb +14 -17
  39. data/lib/r10k/environment/svn.rb +31 -15
  40. data/lib/r10k/errors.rb +33 -22
  41. data/lib/r10k/errors/formatting.rb +28 -0
  42. data/lib/r10k/execution.rb +2 -0
  43. data/lib/r10k/git/cache.rb +1 -6
  44. data/lib/r10k/git/errors.rb +1 -2
  45. data/lib/r10k/git/ref.rb +1 -1
  46. data/lib/r10k/module.rb +1 -1
  47. data/lib/r10k/module/base.rb +94 -2
  48. data/lib/r10k/module/forge.rb +33 -30
  49. data/lib/r10k/module/git.rb +13 -9
  50. data/lib/r10k/module/svn.rb +41 -28
  51. data/lib/r10k/puppetfile.rb +17 -1
  52. data/lib/r10k/semver.rb +2 -0
  53. data/lib/r10k/source/base.rb +8 -0
  54. data/lib/r10k/source/git.rb +1 -1
  55. data/lib/r10k/source/svn.rb +23 -5
  56. data/lib/r10k/svn/remote.rb +23 -3
  57. data/lib/r10k/svn/working_dir.rb +60 -9
  58. data/lib/r10k/task.rb +1 -0
  59. data/lib/r10k/task/deployment.rb +9 -1
  60. data/lib/r10k/task/environment.rb +2 -0
  61. data/lib/r10k/task/module.rb +1 -0
  62. data/lib/r10k/task/puppetfile.rb +3 -0
  63. data/lib/r10k/task_runner.rb +1 -0
  64. data/lib/r10k/util/attempt.rb +84 -0
  65. data/lib/r10k/util/basedir.rb +65 -0
  66. data/lib/r10k/util/purgeable.rb +55 -45
  67. data/lib/r10k/util/setopts.rb +53 -0
  68. data/lib/r10k/util/subprocess.rb +6 -30
  69. data/lib/r10k/util/subprocess/posix/runner.rb +29 -2
  70. data/lib/r10k/util/subprocess/result.rb +17 -4
  71. data/lib/r10k/util/subprocess/subprocess_error.rb +24 -0
  72. data/lib/r10k/version.rb +1 -1
  73. data/r10k.gemspec +7 -29
  74. data/spec/fixtures/unit/puppetfile/invalid-syntax/Puppetfile +1 -0
  75. data/spec/fixtures/unit/puppetfile/load-error/Puppetfile +1 -0
  76. data/spec/matchers/exit_with.rb +28 -0
  77. data/spec/r10k-mocks.rb +3 -0
  78. data/spec/r10k-mocks/mock_config.rb +28 -0
  79. data/spec/r10k-mocks/mock_env.rb +7 -0
  80. data/spec/r10k-mocks/mock_source.rb +10 -0
  81. data/spec/shared-examples/git-ref.rb +7 -7
  82. data/spec/spec_helper.rb +17 -5
  83. data/spec/unit/action/cri_runner_spec.rb +76 -0
  84. data/spec/unit/action/puppetfile/cri_action_spec.rb +65 -0
  85. data/spec/unit/action/runner_spec.rb +64 -0
  86. data/spec/unit/action/visitor_spec.rb +39 -0
  87. data/spec/unit/deployment_spec.rb +142 -0
  88. data/spec/unit/environment/base_spec.rb +38 -0
  89. data/spec/unit/environment/git_spec.rb +40 -10
  90. data/spec/unit/environment/svn_spec.rb +41 -4
  91. data/spec/unit/errors/formatting_spec.rb +84 -0
  92. data/spec/unit/git/alternates_spec.rb +1 -1
  93. data/spec/unit/git/head_spec.rb +1 -1
  94. data/spec/unit/git/ref_spec.rb +1 -1
  95. data/spec/unit/git/working_dir_spec.rb +1 -1
  96. data/spec/unit/module/base_spec.rb +72 -0
  97. data/spec/unit/module/forge_spec.rb +49 -8
  98. data/spec/unit/module/git_spec.rb +78 -0
  99. data/spec/unit/module/svn_spec.rb +40 -4
  100. data/spec/unit/module_spec.rb +3 -3
  101. data/spec/unit/puppetfile_spec.rb +84 -0
  102. data/spec/unit/settings/container_spec.rb +1 -1
  103. data/spec/unit/source/base_spec.rb +31 -0
  104. data/spec/unit/source/git_spec.rb +7 -7
  105. data/spec/unit/source/svn_spec.rb +1 -1
  106. data/spec/unit/svn/working_dir_spec.rb +56 -0
  107. data/spec/unit/util/attempt_spec.rb +82 -0
  108. data/spec/unit/util/setopts_spec.rb +59 -0
  109. data/spec/unit/util/subprocess/result_spec.rb +36 -0
  110. data/spec/unit/util/subprocess/subprocess_error_spec.rb +26 -0
  111. data/spec/unit/util/subprocess_spec.rb +2 -7
  112. metadata +83 -100
  113. data/.nodeset.yml +0 -7
  114. data/.rspec +0 -1
  115. data/README.markdown +0 -276
  116. data/Rakefile +0 -1
  117. data/doc/puppetfile.markdown +0 -87
  118. data/spec/rspec-system-r10k/puppetfile.rb +0 -24
  119. data/spec/rspec-system-r10k/tmpdir.rb +0 -32
  120. data/spec/system-provisioning/el.rb +0 -38
  121. data/spec/system/module/forge/install_spec.rb +0 -51
  122. data/spec/system/module/git/install_spec.rb +0 -117
  123. data/spec/system/module/svn/install_spec.rb +0 -51
  124. data/spec/system/module/svn/update_spec.rb +0 -38
  125. data/spec/system/spec_helper.rb +0 -60
  126. data/spec/system/system-helpers.rb +0 -4
  127. data/spec/system/version_spec.rb +0 -7
@@ -5,19 +5,19 @@ describe R10K::Module do
5
5
  describe 'delegating to R10K::Module::Git' do
6
6
  it "accepts args {:git => 'git url}" do
7
7
  obj = R10K::Module.new('foo', '/modulepath', :git => 'git url')
8
- obj.should be_a_kind_of R10K::Module::Git
8
+ expect(obj).to be_a_kind_of(R10K::Module::Git)
9
9
  end
10
10
  end
11
11
 
12
12
  describe 'delegating to R10K::Module::Git' do
13
13
  it "accepts name matching 'username/modulename' and no args" do
14
14
  obj = R10K::Module.new('bar/quux', '/modulepath', [])
15
- obj.should be_a_kind_of R10K::Module::Forge
15
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
16
16
  end
17
17
 
18
18
  it "accepts name matching 'username/modulename' and a semver argument" do
19
19
  obj = R10K::Module.new('bar/quux', '/modulepath', '10.0.0')
20
- obj.should be_a_kind_of R10K::Module::Forge
20
+ expect(obj).to be_a_kind_of(R10K::Module::Forge)
21
21
  end
22
22
  end
23
23
 
@@ -0,0 +1,84 @@
1
+ require 'spec_helper'
2
+ require 'r10k/puppetfile'
3
+
4
+ describe R10K::Puppetfile do
5
+
6
+ subject do
7
+ described_class.new(
8
+ '/some/nonexistent/basedir'
9
+ )
10
+ end
11
+
12
+ describe "the default moduledir" do
13
+ it "is the basedir joined with '/modules' path" do
14
+ expect(subject.moduledir).to eq '/some/nonexistent/basedir/modules'
15
+ end
16
+ end
17
+
18
+ describe "setting moduledir" do
19
+ it "changes to given moduledir if it is an absolute path" do
20
+ subject.set_moduledir('/absolute/path/moduledir')
21
+ expect(subject.moduledir).to eq '/absolute/path/moduledir'
22
+ end
23
+
24
+ it "joins the basedir with the given moduledir if it is a relative path" do
25
+ subject.set_moduledir('relative/moduledir')
26
+ expect(subject.moduledir).to eq '/some/nonexistent/basedir/relative/moduledir'
27
+ end
28
+ end
29
+
30
+ describe "evaluating a Puppetfile" do
31
+ def expect_wrapped_error(orig, pf_path, wrapped_error)
32
+ expect(orig).to be_a_kind_of(R10K::Error)
33
+ expect(orig.message).to eq("Failed to evaluate #{pf_path}")
34
+ expect(orig.original).to be_a_kind_of(wrapped_error)
35
+ end
36
+
37
+ it "wraps and re-raises syntax errors" do
38
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'invalid-syntax')
39
+ pf_path = File.join(path, 'Puppetfile')
40
+ subject = described_class.new(path)
41
+ expect {
42
+ subject.load!
43
+ }.to raise_error do |e|
44
+ expect_wrapped_error(e, pf_path, SyntaxError)
45
+ end
46
+ end
47
+
48
+ it "wraps and re-raises load errors" do
49
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'load-error')
50
+ pf_path = File.join(path, 'Puppetfile')
51
+ subject = described_class.new(path)
52
+ expect {
53
+ subject.load!
54
+ }.to raise_error do |e|
55
+ expect_wrapped_error(e, pf_path, LoadError)
56
+ end
57
+ end
58
+ end
59
+
60
+ describe "accepting a visitor" do
61
+ it "passes itself to the visitor" do
62
+ visitor = spy('visitor')
63
+ expect(visitor).to receive(:visit).with(:puppetfile, subject)
64
+ subject.accept(visitor)
65
+ end
66
+
67
+ it "passes the visitor to each module if the visitor yields" do
68
+ visitor = spy('visitor')
69
+ expect(visitor).to receive(:visit) do |type, other, &block|
70
+ expect(type).to eq :puppetfile
71
+ expect(other).to eq subject
72
+ block.call
73
+ end
74
+
75
+ mod1 = spy('module')
76
+ expect(mod1).to receive(:accept).with(visitor)
77
+ mod2 = spy('module')
78
+ expect(mod2).to receive(:accept).with(visitor)
79
+
80
+ expect(subject).to receive(:modules).and_return([mod1, mod2])
81
+ subject.accept(visitor)
82
+ end
83
+ end
84
+ end
@@ -12,7 +12,7 @@ describe R10K::Settings::Container do
12
12
 
13
13
  it 'can check if a key is valid' do
14
14
  subject.add_valid_key(:v)
15
- expect(subject.valid_key?(:v)).to be_true
15
+ expect(subject.valid_key?(:v)).to be_truthy
16
16
  end
17
17
 
18
18
  it 'can list all valid keys' do
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+ require 'r10k/source'
3
+
4
+ describe R10K::Source::Base do
5
+ subject { described_class.new('base', '/some/nonexistent/path') }
6
+
7
+ describe "accepting a visitor" do
8
+ it "passes itself to the visitor" do
9
+ visitor = spy('visitor')
10
+ expect(visitor).to receive(:visit).with(:source, subject)
11
+ subject.accept(visitor)
12
+ end
13
+
14
+ it "passes the visitor to each environment if the visitor yields" do
15
+ visitor = spy('visitor')
16
+ expect(visitor).to receive(:visit) do |type, other, &block|
17
+ expect(type).to eq :source
18
+ expect(other).to eq subject
19
+ block.call
20
+ end
21
+
22
+ env1 = spy('environment')
23
+ expect(env1).to receive(:accept).with(visitor)
24
+ env2 = spy('environment')
25
+ expect(env2).to receive(:accept).with(visitor)
26
+
27
+ expect(subject).to receive(:environments).and_return([env1, env2])
28
+ subject.accept(visitor)
29
+ end
30
+ end
31
+ end
@@ -32,14 +32,14 @@ describe R10K::Source::Git do
32
32
  it "generates environments when the cache is present and environments have not been loaded" do
33
33
  allow(subject.cache).to receive(:cached?).and_return true
34
34
  allow(subject).to receive(:generate_environments).and_return %w[hi]
35
- expect(subject.environments).to have(1).items
35
+ expect(subject.environments.size).to eq(1)
36
36
  end
37
37
 
38
38
  it "doesn't recreate environments if they have already been loaded" do
39
39
  allow(subject.cache).to receive(:cached?).and_return true
40
40
  allow(subject).to receive(:generate_environments).once.and_return %w[hi]
41
- expect(subject.environments).to have(1).items
42
- expect(subject.environments).to have(1).items
41
+ expect(subject.environments.size).to eq(1)
42
+ expect(subject.environments.size).to eq(1)
43
43
  end
44
44
  end
45
45
 
@@ -51,7 +51,7 @@ describe R10K::Source::Git do
51
51
  let(:master_env) { subject.generate_environments.first }
52
52
 
53
53
  it "creates an environment for each branch" do
54
- expect(subject.generate_environments).to have(1).items
54
+ expect(subject.generate_environments.size).to eq(1)
55
55
  end
56
56
 
57
57
  it "copies the source remote to the environment" do
@@ -79,7 +79,7 @@ describe R10K::Source::Git, "handling invalid branch names" do
79
79
  end
80
80
 
81
81
  it "creates an environment for each branch" do
82
- expect(subject.generate_environments).to have(2).items
82
+ expect(subject.generate_environments.size).to eq(2)
83
83
  end
84
84
 
85
85
  it "removes invalid characters from branch names" do
@@ -102,7 +102,7 @@ describe R10K::Source::Git, "handling invalid branch names" do
102
102
  end
103
103
 
104
104
  it "only creates an environment for valid branches" do
105
- expect(subject.generate_environments).to have(1).items
105
+ expect(subject.generate_environments.size).to eq(1)
106
106
  end
107
107
  end
108
108
  end
@@ -130,7 +130,7 @@ describe R10K::Source::Git, 'when prefixing is enabled' do
130
130
  let(:environments) { subject.environments }
131
131
 
132
132
  it "creates an environment for each branch" do
133
- expect(subject.environments).to have(2).items
133
+ expect(subject.environments.size).to eq(2)
134
134
  end
135
135
 
136
136
  it "prefixes the source name to environments when prefixing is enabled" do
@@ -83,7 +83,7 @@ describe R10K::Source::SVN, 'when prefixing is enabled' do
83
83
  let(:environments) { subject.generate_environments }
84
84
 
85
85
  it "creates an environment for each branch and the trunk" do
86
- expect(environments).to have(4).items
86
+ expect(environments.size).to eq(4)
87
87
  end
88
88
 
89
89
  it "prefixes the source name to environments" do
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+ require 'r10k/svn/working_dir'
3
+
4
+ describe R10K::SVN::WorkingDir, "initializing" do
5
+ let(:pathname) { Pathname.new("/some/imaginary/path") }
6
+ it "stores the provided path" do
7
+ subject = described_class.new(pathname)
8
+ expect(subject.path).to eq Pathname.new("/some/imaginary/path")
9
+ end
10
+
11
+ describe "when auth is provided" do
12
+ it "raises an error when only the username is provided" do
13
+ expect {
14
+ described_class.new(pathname, :username => "root")
15
+ }.to raise_error(ArgumentError, "Both username and password must be specified")
16
+ end
17
+
18
+ it "raises an error when only the password is provided" do
19
+ expect {
20
+ described_class.new(pathname, :password => "hunter2")
21
+ }.to raise_error(ArgumentError, "Both username and password must be specified")
22
+ end
23
+
24
+ it "does not raise an error when both username and password are provided" do
25
+ o = described_class.new(pathname, :username => "root", :password => "hunter2")
26
+ expect(o.username).to eq("root")
27
+ expect(o.password).to eq("hunter2")
28
+ end
29
+ end
30
+ end
31
+
32
+ describe R10K::SVN::WorkingDir, "when authentication credentials are given" do
33
+ let(:pathname) { Pathname.new("/some/imaginary/path") }
34
+ subject { described_class.new(pathname, :username => "root", :password => "hunter2") }
35
+
36
+ def check_args(args)
37
+ expect(args).to include("--username")
38
+ expect(args).to include("root")
39
+ expect(args).to include("--password")
40
+ expect(args).to include("hunter2")
41
+ end
42
+
43
+ it "invokes 'svn checkout' with the given credentials" do
44
+ expect(subject).to receive(:svn) do |args, _|
45
+ check_args(args)
46
+ end
47
+ subject.checkout('https://some.svn.url/trunk')
48
+ end
49
+
50
+ it "invokes 'svn update' with the given credentials" do
51
+ expect(subject).to receive(:svn) do |args, _|
52
+ check_args(args)
53
+ end
54
+ subject.update
55
+ end
56
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+ require 'r10k/util/attempt'
3
+
4
+ describe R10K::Util::Attempt do
5
+
6
+ describe "with a single truthy value" do
7
+ subject(:attempt) { described_class.new("hello") }
8
+
9
+ it "invokes the next action with the value" do
10
+ value = nil
11
+ attempt.try { |inner| value = inner }
12
+ attempt.run
13
+ expect(attempt).to be_ok
14
+ expect(value).to eq "hello"
15
+ end
16
+
17
+ it "returns the resulting value from the block" do
18
+ attempt.try { |inner| inner + " world" }
19
+ result = attempt.run
20
+ expect(attempt).to be_ok
21
+ expect(result).to eq "hello world"
22
+ end
23
+ end
24
+
25
+ describe "with a false value" do
26
+ subject(:attempt) { described_class.new(nil) }
27
+
28
+ it "does not evaluate the block" do
29
+ value = "outside of block"
30
+ attempt.try { |inner| value = "ran block" }
31
+ attempt.run
32
+ expect(attempt).to be_ok
33
+ expect(value).to eq "outside of block"
34
+ end
35
+
36
+ it "does not continue execution" do
37
+ attempt.try { |_| "something" }.try { raise }
38
+ expect(attempt.run).to be_nil
39
+ end
40
+ end
41
+
42
+ describe "with an array" do
43
+ subject(:attempt) { described_class.new([1, 2, 3, 4, 5]) }
44
+
45
+ it "runs the block for each element in the array" do
46
+ sum = 0
47
+ attempt.try { |inner| sum += inner }
48
+ attempt.run
49
+ expect(attempt).to be_ok
50
+ expect(sum).to eq 15
51
+ end
52
+
53
+ it "returns the result of the operation on each array member" do
54
+ sum = 0
55
+ attempt.try { |inner| sum += inner }
56
+ result = attempt.run
57
+ expect(result).to eq([1, 3, 6, 10, 15])
58
+ end
59
+ end
60
+
61
+ describe "when an exception is raised" do
62
+ subject(:attempt) { described_class.new("initial") }
63
+
64
+ it "returns the exception" do
65
+ attempt.try { |_| raise RuntimeError }
66
+ result = attempt.run
67
+ expect(attempt).to_not be_ok
68
+ expect(result).to be_a_kind_of RuntimeError
69
+ end
70
+
71
+ it "does not continue execution" do
72
+ attempt.try { |_| raise RuntimeError }.try { |_| "This should not be run" }
73
+ result = attempt.run
74
+ expect(result).to be_a_kind_of RuntimeError
75
+ end
76
+
77
+ it "only rescues descendants of StandardError" do
78
+ attempt.try { |_| raise Exception }
79
+ expect { attempt.run }.to raise_error
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'r10k/util/setopts'
3
+
4
+ describe R10K::Util::Setopts do
5
+ let(:klass) do
6
+ Class.new do
7
+ include R10K::Util::Setopts
8
+
9
+ attr_reader :valid, :alsovalid, :truthyvalid
10
+
11
+ def initialize(opts = {})
12
+ setopts(opts, {
13
+ :valid => :self, :alsovalid => :self, :truthyvalid => true,
14
+ :validalias => :valid,
15
+ :ignoreme => nil
16
+ })
17
+ end
18
+ end
19
+ end
20
+
21
+ it "can handle an empty hash of options" do
22
+ o = klass.new()
23
+ expect(o.valid).to be_nil
24
+ expect(o.alsovalid).to be_nil
25
+ end
26
+
27
+ it "can handle a single valid option" do
28
+ o = klass.new(:valid => 'yep')
29
+ expect(o.valid).to eq 'yep'
30
+ expect(o.alsovalid).to be_nil
31
+ end
32
+
33
+ it "can handle multiple valid options" do
34
+ o = klass.new(:valid => 'yep', :alsovalid => 'yarp')
35
+ expect(o.valid).to eq 'yep'
36
+ expect(o.alsovalid).to eq 'yarp'
37
+ end
38
+
39
+ it "can handle options marked with TrueClass" do
40
+ o = klass.new(:truthyvalid => 'so truthy')
41
+ expect(o.truthyvalid).to eq 'so truthy'
42
+ end
43
+
44
+ it "can handle aliases marked with :self" do
45
+ o = klass.new(:validalias => 'yuuup')
46
+ expect(o.valid).to eq 'yuuup'
47
+ end
48
+
49
+
50
+ it "raises an error when given an unhandled option" do
51
+ expect {
52
+ klass.new(:valid => 'yep', :notvalid => 'newp')
53
+ }.to raise_error(ArgumentError, /cannot handle option 'notvalid'/)
54
+ end
55
+
56
+ it "ignores values that are marked as unhandled" do
57
+ klass.new(:ignoreme => "IGNORE ME!")
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+ require 'r10k/util/subprocess'
3
+
4
+ describe R10K::Util::Subprocess::Result do
5
+ describe "formatting" do
6
+ it "includes the exit code" do
7
+ result = described_class.new(%w[/usr/bin/gti --zoom], '', '', 42)
8
+ expect(result.format).to match(%r[Exit code: 42])
9
+ end
10
+
11
+ describe "stdout" do
12
+ it "is omitted when empty" do
13
+ result = described_class.new(%w[/usr/bin/gti --zoom], '', '', 42)
14
+ expect(result.format).to_not match(%r[Stdout])
15
+ end
16
+ it "is included when non-empty" do
17
+ result = described_class.new(%w[/usr/bin/gti --zoom], 'stuff here', '', 42)
18
+ expect(result.format).to match(%r[Stdout:])
19
+ expect(result.format).to match(%r[stuff here])
20
+ end
21
+ end
22
+
23
+ describe "stderr" do
24
+ it "is omitted when empty" do
25
+ result = described_class.new(%w[/usr/bin/gti --zoom], '', '', 42)
26
+ expect(result.format).to_not match(%r[Stderr])
27
+ end
28
+
29
+ it "is included when non-empty" do
30
+ result = described_class.new(%w[/usr/bin/gti --zoom], '', 'other stuff', 42)
31
+ expect(result.format).to match(%r[Stderr:])
32
+ expect(result.format).to match(%r[other stuff])
33
+ end
34
+ end
35
+ end
36
+ end