cf 0.1.5 → 0.6.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. data/LICENSE +1277 -30
  2. data/Rakefile +12 -1
  3. data/bin/cf +0 -3
  4. data/lib/cf.rb +6 -0
  5. data/lib/cf/cli.rb +389 -190
  6. data/lib/cf/cli/app/app.rb +45 -0
  7. data/lib/cf/cli/app/apps.rb +99 -0
  8. data/lib/cf/cli/app/base.rb +90 -0
  9. data/lib/cf/cli/app/crashes.rb +42 -0
  10. data/lib/cf/cli/app/delete.rb +95 -0
  11. data/lib/cf/cli/app/deprecated.rb +11 -0
  12. data/lib/cf/cli/app/env.rb +78 -0
  13. data/lib/cf/cli/app/files.rb +137 -0
  14. data/lib/cf/cli/app/health.rb +26 -0
  15. data/lib/cf/cli/app/instances.rb +53 -0
  16. data/lib/cf/cli/app/logs.rb +76 -0
  17. data/lib/cf/cli/app/push.rb +105 -0
  18. data/lib/cf/cli/app/push/create.rb +149 -0
  19. data/lib/cf/cli/app/push/interactions.rb +94 -0
  20. data/lib/cf/cli/app/push/sync.rb +64 -0
  21. data/lib/cf/cli/app/rename.rb +35 -0
  22. data/lib/cf/cli/app/restart.rb +20 -0
  23. data/lib/cf/cli/app/scale.rb +69 -0
  24. data/lib/cf/cli/app/start.rb +143 -0
  25. data/lib/cf/cli/app/stats.rb +67 -0
  26. data/lib/cf/cli/app/stop.rb +27 -0
  27. data/lib/cf/cli/domain/base.rb +8 -0
  28. data/lib/cf/cli/domain/domains.rb +40 -0
  29. data/lib/cf/cli/domain/map.rb +55 -0
  30. data/lib/cf/cli/domain/unmap.rb +56 -0
  31. data/lib/cf/cli/help.rb +15 -0
  32. data/lib/cf/cli/interactive.rb +105 -0
  33. data/lib/cf/cli/organization/base.rb +12 -0
  34. data/lib/cf/cli/organization/create.rb +32 -0
  35. data/lib/cf/cli/organization/delete.rb +73 -0
  36. data/lib/cf/cli/organization/org.rb +45 -0
  37. data/lib/cf/cli/organization/orgs.rb +35 -0
  38. data/lib/cf/cli/organization/rename.rb +36 -0
  39. data/lib/cf/cli/route/base.rb +8 -0
  40. data/lib/cf/cli/route/map.rb +70 -0
  41. data/lib/cf/cli/route/routes.rb +26 -0
  42. data/lib/cf/cli/route/unmap.rb +62 -0
  43. data/lib/cf/cli/service/base.rb +8 -0
  44. data/lib/cf/cli/service/bind.rb +44 -0
  45. data/lib/cf/cli/service/create.rb +107 -0
  46. data/lib/cf/cli/service/delete.rb +82 -0
  47. data/lib/cf/cli/service/rename.rb +35 -0
  48. data/lib/cf/cli/service/service.rb +40 -0
  49. data/lib/cf/cli/service/services.rb +99 -0
  50. data/lib/cf/cli/service/unbind.rb +38 -0
  51. data/lib/cf/cli/space/base.rb +19 -0
  52. data/lib/cf/cli/space/create.rb +63 -0
  53. data/lib/cf/cli/space/delete.rb +95 -0
  54. data/lib/cf/cli/space/rename.rb +39 -0
  55. data/lib/cf/cli/space/space.rb +64 -0
  56. data/lib/cf/cli/space/spaces.rb +55 -0
  57. data/lib/cf/cli/space/switch.rb +16 -0
  58. data/lib/cf/cli/start/base.rb +93 -0
  59. data/lib/cf/cli/start/colors.rb +13 -0
  60. data/lib/cf/cli/start/info.rb +124 -0
  61. data/lib/cf/cli/start/login.rb +94 -0
  62. data/lib/cf/cli/start/logout.rb +17 -0
  63. data/lib/cf/cli/start/target.rb +69 -0
  64. data/lib/cf/cli/start/target_interactions.rb +37 -0
  65. data/lib/cf/cli/start/targets.rb +16 -0
  66. data/lib/cf/cli/user/base.rb +29 -0
  67. data/lib/cf/cli/user/create.rb +39 -0
  68. data/lib/cf/cli/user/passwd.rb +43 -0
  69. data/lib/cf/cli/user/register.rb +42 -0
  70. data/lib/cf/cli/user/users.rb +32 -0
  71. data/lib/cf/constants.rb +10 -7
  72. data/lib/cf/detect.rb +113 -48
  73. data/lib/cf/errors.rb +17 -0
  74. data/lib/cf/plugin.rb +28 -12
  75. data/lib/cf/spacing.rb +89 -0
  76. data/lib/cf/spec_helper.rb +1 -0
  77. data/lib/cf/test_support.rb +6 -0
  78. data/lib/cf/version.rb +1 -1
  79. data/spec/assets/hello-sinatra/Gemfile +3 -0
  80. data/spec/assets/hello-sinatra/Gemfile.lock +17 -0
  81. data/spec/assets/hello-sinatra/config.ru +3 -0
  82. data/spec/assets/hello-sinatra/fat-cat-makes-app-larger.png +0 -0
  83. data/spec/assets/hello-sinatra/main.rb +6 -0
  84. data/spec/assets/specker_runner/specker_runner_input.rb +6 -0
  85. data/spec/assets/specker_runner/specker_runner_pause.rb +5 -0
  86. data/spec/cf/cli/app/base_spec.rb +17 -0
  87. data/spec/cf/cli/app/delete_spec.rb +188 -0
  88. data/spec/cf/cli/app/instances_spec.rb +65 -0
  89. data/spec/cf/cli/app/push/create_spec.rb +661 -0
  90. data/spec/cf/cli/app/push_spec.rb +369 -0
  91. data/spec/cf/cli/app/rename_spec.rb +104 -0
  92. data/spec/cf/cli/app/scale_spec.rb +75 -0
  93. data/spec/cf/cli/app/start_spec.rb +208 -0
  94. data/spec/cf/cli/app/stats_spec.rb +68 -0
  95. data/spec/cf/cli/domain/map_spec.rb +130 -0
  96. data/spec/cf/cli/domain/unmap_spec.rb +69 -0
  97. data/spec/cf/cli/organization/orgs_spec.rb +108 -0
  98. data/spec/cf/cli/organization/rename_spec.rb +113 -0
  99. data/spec/cf/cli/route/map_spec.rb +121 -0
  100. data/spec/cf/cli/route/unmap_spec.rb +155 -0
  101. data/spec/cf/cli/service/bind_spec.rb +25 -0
  102. data/spec/cf/cli/service/delete_spec.rb +22 -0
  103. data/spec/cf/cli/service/rename_spec.rb +105 -0
  104. data/spec/cf/cli/service/service_spec.rb +23 -0
  105. data/spec/cf/cli/service/unbind_spec.rb +25 -0
  106. data/spec/cf/cli/space/create_spec.rb +93 -0
  107. data/spec/cf/cli/space/rename_spec.rb +102 -0
  108. data/spec/cf/cli/space/spaces_spec.rb +104 -0
  109. data/spec/cf/cli/space/switch_space_spec.rb +55 -0
  110. data/spec/cf/cli/start/info_spec.rb +160 -0
  111. data/spec/cf/cli/start/login_spec.rb +142 -0
  112. data/spec/cf/cli/start/logout_spec.rb +50 -0
  113. data/spec/cf/cli/start/target_spec.rb +123 -0
  114. data/spec/cf/cli/user/create_spec.rb +54 -0
  115. data/spec/cf/cli/user/passwd_spec.rb +102 -0
  116. data/spec/cf/cli/user/register_spec.rb +140 -0
  117. data/spec/cf/cli_spec.rb +442 -0
  118. data/spec/cf/detect_spec.rb +54 -0
  119. data/spec/console_app_specker/console_app_specker_matchers_spec.rb +173 -0
  120. data/spec/console_app_specker/specker_runner_spec.rb +167 -0
  121. data/spec/features/account_lifecycle_spec.rb +85 -0
  122. data/spec/features/login_spec.rb +66 -0
  123. data/spec/features/push_flow_spec.rb +125 -0
  124. data/spec/features/switching_targets_spec.rb +32 -0
  125. data/spec/spec_helper.rb +72 -0
  126. data/spec/support/command_helper.rb +81 -0
  127. data/spec/support/config_helper.rb +15 -0
  128. data/spec/support/console_app_specker_matchers.rb +86 -0
  129. data/spec/support/fake_home_dir.rb +55 -0
  130. data/spec/support/interact_helper.rb +29 -0
  131. data/spec/support/shared_examples/errors.rb +40 -0
  132. data/spec/support/shared_examples/input.rb +14 -0
  133. data/spec/support/specker_runner.rb +80 -0
  134. data/spec/support/tracking_expector.rb +71 -0
  135. metadata +427 -66
  136. data/lib/cf/cli/app.rb +0 -595
  137. data/lib/cf/cli/command.rb +0 -444
  138. data/lib/cf/cli/dots.rb +0 -133
  139. data/lib/cf/cli/service.rb +0 -112
  140. data/lib/cf/cli/user.rb +0 -71
@@ -9,12 +9,25 @@ module CF
9
9
  @@plugins = []
10
10
 
11
11
  def self.load_all
12
- # auto-load gems with 'vmc-plugin' in their name
13
- enabled =
14
- Set.new(
15
- Gem::Specification.find_all { |s|
16
- s.name =~ /vmc-plugin/
17
- }.collect(&:name))
12
+ # auto-load gems with 'cf-plugin' in their name
13
+ matching =
14
+ if Gem::Specification.respond_to? :find_all
15
+ Gem::Specification.find_all do |s|
16
+ s.name =~ /cf-plugin/
17
+ end
18
+ else
19
+ Gem.source_index.find_name(/cf-plugin/)
20
+ end
21
+
22
+ enabled = Set.new(matching.collect(&:name))
23
+
24
+ cf_gems = Gem.loaded_specs["cf"]
25
+ ((cf_gems && cf_gems.dependencies) || Gem.loaded_specs.values).each do |dep|
26
+ if dep.name =~ /cf-plugin/
27
+ require "#{dep.name}/plugin"
28
+ enabled.delete dep.name
29
+ end
30
+ end
18
31
 
19
32
  # allow explicit enabling/disabling of gems via config
20
33
  plugins = File.expand_path(CF::PLUGINS_FILE)
@@ -28,13 +41,16 @@ module CF
28
41
  # we require this file specifically so people can require the gem
29
42
  # without it plugging into CF
30
43
  enabled.each do |gemname|
31
- require "#{gemname}/plugin"
44
+ begin
45
+ require "#{gemname}/plugin"
46
+ rescue Gem::LoadError => e
47
+ puts "Failed to load #{gemname}:"
48
+ puts " #{e}"
49
+ puts
50
+ puts "You may need to update or remove this plugin."
51
+ puts
52
+ end
32
53
  end
33
54
  end
34
55
  end
35
-
36
- def self.Plugin(target = CLI, &blk)
37
- # SUPER FANCY PLUGIN SYSTEM
38
- target.class_eval &blk
39
- end
40
56
  end
@@ -0,0 +1,89 @@
1
+ module CF
2
+ module Spacing
3
+ @@indentation = 0
4
+
5
+ def indented
6
+ @@indentation += 1
7
+ yield
8
+ ensure
9
+ @@indentation -= 1
10
+ end
11
+
12
+ def line(msg = "")
13
+ return puts "" if msg.empty?
14
+
15
+ start_line(msg)
16
+ puts ""
17
+ end
18
+
19
+ def start_line(msg)
20
+ print " " * @@indentation unless quiet?
21
+ print msg
22
+ end
23
+
24
+ def lines(blob)
25
+ blob.each_line do |line|
26
+ start_line(line)
27
+ end
28
+
29
+ line
30
+ end
31
+
32
+ def quiet?
33
+ false
34
+ end
35
+
36
+ def spaced(vals)
37
+ num = 0
38
+ vals.each do |val|
39
+ line unless quiet? || num == 0
40
+ yield val
41
+ num += 1
42
+ end
43
+ end
44
+
45
+ def tabular(*rows)
46
+ spacings = []
47
+ rows.each do |row|
48
+ next unless row
49
+
50
+ row.each.with_index do |col, i|
51
+ next unless col
52
+
53
+ width = text_width(col)
54
+
55
+ if !spacings[i] || width > spacings[i]
56
+ spacings[i] = width
57
+ end
58
+ end
59
+ end
60
+
61
+ columns = spacings.size
62
+ rows.each do |row|
63
+ next unless row
64
+
65
+ row.each.with_index do |col, i|
66
+ next unless col
67
+
68
+ start_line justify(col, spacings[i])
69
+ print " " unless i + 1 == columns
70
+ end
71
+
72
+ line
73
+ end
74
+ end
75
+
76
+ def trim_escapes(str)
77
+ str.gsub(/\e\[\d+m/, "")
78
+ end
79
+
80
+ def text_width(str)
81
+ trim_escapes(str).size
82
+ end
83
+
84
+ def justify(str, width)
85
+ trimmed = trim_escapes(str)
86
+ str.ljust(width + (str.size - trimmed.size))
87
+ end
88
+ end
89
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path("../../../spec/spec_helper", __FILE__)
@@ -0,0 +1,6 @@
1
+ module CF::TestSupport
2
+ end
3
+
4
+ Dir[File.expand_path('../../../spec/support/**/*.rb', __FILE__)].each do |file|
5
+ require file
6
+ end
@@ -1,3 +1,3 @@
1
1
  module CF
2
- VERSION = "0.1.5"
2
+ VERSION = "0.6.0.rc1".freeze
3
3
  end
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "sinatra"
@@ -0,0 +1,17 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ rack (1.5.2)
5
+ rack-protection (1.3.2)
6
+ rack
7
+ sinatra (1.3.4)
8
+ rack (~> 1.4)
9
+ rack-protection (~> 1.3)
10
+ tilt (~> 1.3, >= 1.3.3)
11
+ tilt (1.3.3)
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ sinatra
@@ -0,0 +1,3 @@
1
+ require './main'
2
+
3
+ run Main.new
@@ -0,0 +1,6 @@
1
+ require "rubygems"
2
+ require "sinatra"
3
+
4
+ get "/" do
5
+ "Hello, world!"
6
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true
4
+ print "started"
5
+ typed = gets
6
+ print "received #{typed}"
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ print "started"
4
+ sleep 0.5
5
+ print " finished"
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require "cf/cli/app/base"
3
+
4
+ describe CF::App::Base do
5
+ describe '#human_size' do
6
+ let(:base) { CF::App::Base.new }
7
+
8
+ it { base.human_size(1_023).should == "1023.0B" }
9
+ it { base.human_size(1_024).should == "1.0K" }
10
+ it { base.human_size(1_024 * 1_024).should == "1.0M" }
11
+ it { base.human_size(1_024 * 1_024 * 1.5).should == "1.5M" }
12
+ it { base.human_size(1_024 * 1_024 * 1_024).should == "1.0G" }
13
+ it { base.human_size(1_024 * 1_024 * 1_024 * 1.5).should == "1.5G" }
14
+ it { base.human_size(1_024 * 1_024 * 1_024 * 1.234, 3).should == "1.234G" }
15
+ it { base.human_size(31395840).should == "29.9M" } # tests against floating point errors
16
+ end
17
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+ require "cf/cli/app/delete"
3
+
4
+ describe CF::App::Delete do
5
+ let(:global) { { :color => false, :quiet => true } }
6
+ let(:inputs) { {} }
7
+ let(:given) { {} }
8
+ let(:client) { fake_client }
9
+ let(:app) {}
10
+ let(:new_name) { "some-new-name" }
11
+
12
+ before do
13
+ any_instance_of(CF::CLI) do |cli|
14
+ stub(cli).client { client }
15
+ stub(cli).precondition { nil }
16
+ end
17
+ end
18
+
19
+ subject { Mothership.new.invoke(:delete, inputs, given, global) }
20
+
21
+ describe 'metadata' do
22
+ let(:command) { Mothership.commands[:delete] }
23
+
24
+ describe 'command' do
25
+ subject { command }
26
+ its(:description) { should eq "Delete an application" }
27
+ it { expect(Mothership::Help.group(:apps, :manage)).to include(subject) }
28
+ end
29
+
30
+ include_examples 'inputs must have descriptions'
31
+
32
+ describe 'arguments' do
33
+ subject { command.arguments }
34
+ it 'has the correct argument order' do
35
+ should eq([{ :type => :splat, :value => nil, :name => :apps }])
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'when there are no apps' do
41
+ context 'and an app is given' do
42
+ let(:given) { { :app => "some-app" } }
43
+ it { expect { subject }.to raise_error(CF::UserError, "Unknown app 'some-app'.") }
44
+ end
45
+
46
+ context 'and an app is not given' do
47
+ it { expect { subject }.to raise_error(CF::UserError, "No applications.") }
48
+ end
49
+ end
50
+
51
+ context 'when there are apps' do
52
+ let(:client) { fake_client(:apps => apps) }
53
+ let(:apps) { [basic_app, app_with_orphans, app_without_orphans] }
54
+ let(:service_1) { fake :service_instance }
55
+ let(:service_2) { fake :service_instance }
56
+ let(:basic_app) { fake(:app, :name => "basic_app") }
57
+ let(:app_with_orphans) {
58
+ fake :app,
59
+ :name => "app_with_orphans",
60
+ :service_bindings => [
61
+ fake(:service_binding, :service_instance => service_1),
62
+ fake(:service_binding, :service_instance => service_2)
63
+ ]
64
+ }
65
+ let(:app_without_orphans) {
66
+ fake :app,
67
+ :name => "app_without_orphans",
68
+ :service_bindings => [
69
+ fake(:service_binding, :service_instance => service_1)
70
+ ]
71
+ }
72
+
73
+ context 'and no app is given' do
74
+ it 'asks for the app' do
75
+ mock_ask("Delete which application?", anything) { basic_app }
76
+ stub_ask { true }
77
+ stub(basic_app).delete!
78
+ subject
79
+ end
80
+ end
81
+
82
+ context 'and a basic app is given' do
83
+ let(:deleted_app) { basic_app }
84
+ let(:given) { { :app => deleted_app.name } }
85
+
86
+ context 'and it asks for confirmation' do
87
+ context 'and the user answers no' do
88
+ it 'does not delete the application' do
89
+ mock_ask("Really delete #{deleted_app.name}?", anything) { false }
90
+ dont_allow(deleted_app).delete!
91
+ subject
92
+ end
93
+ end
94
+
95
+ context 'and the user answers yes' do
96
+ it 'deletes the application' do
97
+ mock_ask("Really delete #{deleted_app.name}?", anything) { true }
98
+ mock(deleted_app).delete!
99
+ subject
100
+ end
101
+ end
102
+ end
103
+
104
+ context 'and --force is given' do
105
+ let(:global) { { :force => true, :color => false, :quiet => true } }
106
+
107
+ it 'deletes the application without asking to confirm' do
108
+ dont_allow_ask
109
+ mock(deleted_app).delete!
110
+ subject
111
+ end
112
+ end
113
+ end
114
+
115
+ context 'and an app with orphaned services is given' do
116
+ let(:deleted_app) { app_with_orphans }
117
+ let(:inputs) { { :app => deleted_app } }
118
+
119
+ context 'and it asks for confirmation' do
120
+ context 'and the user answers yes' do
121
+ it 'asks to delete orphaned services' do
122
+ stub_ask("Really delete #{deleted_app.name}?", anything) { true }
123
+ stub(deleted_app).delete!
124
+
125
+ stub(service_2).invalidate!
126
+
127
+ mock_ask("Delete orphaned service #{service_2.name}?", anything) { true }
128
+
129
+ any_instance_of(CF::App::Delete) do |del|
130
+ mock(del).invoke :delete_service, :service => service_2,
131
+ :really => true
132
+ end
133
+
134
+ subject
135
+ end
136
+ end
137
+
138
+ context 'and the user answers no' do
139
+ it 'does not ask to delete orphaned serivces, or delete them' do
140
+ stub_ask("Really delete #{deleted_app.name}?", anything) { false }
141
+ dont_allow(deleted_app).delete!
142
+
143
+ stub(service_2).invalidate!
144
+
145
+ dont_allow_ask("Delete orphaned service #{service_2.name}?")
146
+
147
+ any_instance_of(CF::App::Delete) do |del|
148
+ dont_allow(del).invoke(:delete_service, anything)
149
+ end
150
+
151
+ subject
152
+ end
153
+ end
154
+ end
155
+
156
+ context 'and --force is given' do
157
+ let(:global) { { :force => true, :color => false, :quiet => true } }
158
+
159
+ it 'does not delete orphaned services' do
160
+ dont_allow_ask
161
+ stub(deleted_app).delete!
162
+
163
+ any_instance_of(CF::App::Delete) do |del|
164
+ dont_allow(del).invoke(:delete_service, anything)
165
+ end
166
+
167
+ subject
168
+ end
169
+ end
170
+
171
+ context 'and --delete-orphaned is given' do
172
+ let(:inputs) { { :app => deleted_app, :delete_orphaned => true } }
173
+
174
+ it 'deletes the orphaned services' do
175
+ stub_ask("Really delete #{deleted_app.name}?", anything) { true }
176
+ stub(deleted_app).delete!
177
+
178
+ any_instance_of(CF::App::Delete) do |del|
179
+ mock(del).invoke :delete_service, :service => service_2,
180
+ :really => true
181
+ end
182
+
183
+ subject
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'stringio'
3
+
4
+ describe CF::App::Stats do
5
+ let(:global) { { :color => false } }
6
+ let(:inputs) { { :app => apps[0] } }
7
+ let(:given) { {} }
8
+ let(:client) { fake_client(:apps => apps) }
9
+ let(:apps) { [fake(:app, :name => "basic_app")] }
10
+ let(:time) { Time.local(2012, 11, 1, 2, 30) }
11
+
12
+ before do
13
+ any_instance_of(CF::CLI) do |cli|
14
+ stub(cli).client { client }
15
+ stub(cli).precondition { nil }
16
+ end
17
+ stub(client.base).instances(anything) do
18
+ {
19
+ "12" => {:state => "STOPPED", :since => time.to_i, :debug_ip => "foo", :debug_port => "bar", :console_ip => "baz", :console_port => "qux"},
20
+ "1" => {:state => "STOPPED", :since => time.to_i, :debug_ip => "foo", :debug_port => "bar", :console_ip => "baz", :console_port => "qux"},
21
+ "2" => {:state => "STARTED", :since => time.to_i, :debug_ip => "foo", :debug_port => "bar", :console_ip => "baz", :console_port => "qux"}
22
+ }
23
+ end
24
+ end
25
+
26
+ subject do
27
+ capture_output do
28
+ Mothership.new.invoke(:instances, inputs, given, global)
29
+ end
30
+ end
31
+
32
+ describe 'metadata' do
33
+ let(:command) { Mothership.commands[:instances] }
34
+
35
+ describe 'command' do
36
+ subject { command }
37
+ its(:description) { should eq "List an app's instances" }
38
+ it { expect(Mothership::Help.group(:apps, :info)).to include(subject) }
39
+ end
40
+
41
+ include_examples 'inputs must have descriptions'
42
+
43
+ describe 'arguments' do
44
+ subject { command.arguments }
45
+ it 'has no arguments' do
46
+ should eq([{:type=>:splat, :value=>nil, :name=>:apps}])
47
+ end
48
+ end
49
+ end
50
+
51
+ it 'prints out the instances in the correct order' do
52
+ subject
53
+ expect(output).to say("instance #1")
54
+ expect(output).to say("instance #2")
55
+ expect(output).to say("instance #12")
56
+ end
57
+
58
+ it 'prints out one of the instances correctly' do
59
+ subject
60
+ expect(output).to say("instance #2: started")
61
+ expect(output).to say(" started: #{time.strftime("%F %r")}")
62
+ expect(output).to say(" debugger: port bar at foo")
63
+ expect(output).to say(" console: port qux at baz")
64
+ end
65
+ end