vmc 0.5.0.rc1 → 0.5.0.rc2

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 (38) hide show
  1. data/lib/vmc/cli.rb +2 -1
  2. data/lib/vmc/cli/app/start.rb +81 -24
  3. data/lib/vmc/cli/space/create.rb +4 -0
  4. data/lib/vmc/cli/space/switch.rb +16 -0
  5. data/lib/vmc/cli/start/base.rb +21 -18
  6. data/lib/vmc/cli/user/base.rb +1 -1
  7. data/lib/vmc/cli/user/delete.rb +2 -0
  8. data/lib/vmc/version.rb +1 -1
  9. data/spec/assets/hello-sinatra/Gemfile.lock +17 -0
  10. data/spec/features/{new_user_flow_spec.rb → v1/new_user_flow_spec.rb} +12 -18
  11. data/spec/features/v2/account_lifecycle_spec.rb +64 -0
  12. data/spec/features/v2/push_flow_spec.rb +126 -0
  13. data/spec/spec_helper.rb +16 -3
  14. data/{lib/vmc/test_support → spec/support}/command_helper.rb +5 -5
  15. data/spec/support/config_helper.rb +15 -0
  16. data/spec/support/console_app_specker_matchers.rb +2 -2
  17. data/spec/support/fake_home_dir.rb +42 -0
  18. data/{lib/vmc/test_support → spec/support}/interact_helper.rb +1 -1
  19. data/spec/support/shared_examples/errors.rb +40 -0
  20. data/{lib/vmc/test_support/common_input_examples.rb → spec/support/shared_examples/input.rb} +0 -0
  21. data/spec/support/specker_runner.rb +17 -61
  22. data/spec/support/tracking_expector.rb +71 -0
  23. data/spec/vmc/cli/app/instances_spec.rb +3 -3
  24. data/spec/vmc/cli/app/start_spec.rb +201 -0
  25. data/spec/vmc/cli/app/stats_spec.rb +21 -15
  26. data/spec/vmc/cli/space/create_spec.rb +73 -0
  27. data/spec/vmc/cli/space/switch_space_spec.rb +55 -0
  28. data/spec/vmc/cli/start/info_spec.rb +3 -16
  29. data/spec/vmc/cli/start/login_spec.rb +97 -37
  30. data/spec/vmc/cli/start/logout_spec.rb +3 -16
  31. data/spec/vmc/cli/start/target_spec.rb +84 -0
  32. data/spec/vmc/cli/user/delete_spec.rb +51 -0
  33. data/spec/vmc/cli/user/passwd_spec.rb +1 -1
  34. data/spec/vmc/cli/user/register_spec.rb +1 -1
  35. data/spec/vmc/cli_spec.rb +38 -36
  36. metadata +65 -39
  37. data/lib/vmc/cli/space/take.rb +0 -16
  38. data/lib/vmc/test_support/fake_home_dir.rb +0 -16
data/spec/spec_helper.rb CHANGED
@@ -6,6 +6,18 @@ require "cfoundry/test_support"
6
6
  require "vmc"
7
7
  require "vmc/test_support"
8
8
  require "webmock"
9
+ require "ostruct"
10
+
11
+ INTEGRATE_WITH = ENV["INTEGRATE_WITH"] || "default"
12
+
13
+ def vmc_bin
14
+ vmc = File.expand_path("#{SPEC_ROOT}/../bin/vmc.dev")
15
+ if INTEGRATE_WITH != 'default'
16
+ "rvm #{INTEGRATE_WITH}@vmc do #{vmc}"
17
+ else
18
+ vmc
19
+ end
20
+ end
9
21
 
10
22
  Dir[File.expand_path('../support/**/*.rb', __FILE__)].each do |file|
11
23
  require file
@@ -20,9 +32,10 @@ RSpec.configure do |c|
20
32
  c.filter_run_excluding :ruby19 => true
21
33
  end
22
34
 
23
- c.include VMC::TestSupport::FakeHomeDir
24
- c.include VMC::TestSupport::CommandHelper
25
- c.include VMC::TestSupport::InteractHelper
35
+ c.include FakeHomeDir
36
+ c.include CommandHelper
37
+ c.include InteractHelper
38
+ c.include ConfigHelper
26
39
 
27
40
  c.before(:all) do
28
41
  WebMock.disable_net_connect!
@@ -1,4 +1,4 @@
1
- module VMC::TestSupport::CommandHelper
1
+ module CommandHelper
2
2
  def vmc(argv)
3
3
  Mothership.new.exit_status 0
4
4
  stub(VMC::CLI).exit { |code| code }
@@ -20,13 +20,13 @@ module VMC::TestSupport::CommandHelper
20
20
  attr_reader :stdout, :stderr, :status
21
21
 
22
22
  def capture_output
23
- real_stdout = $stdout
24
- real_stderr = $stderr
23
+ $real_stdout = $stdout
24
+ $real_stderr = $stderr
25
25
  $stdout = @stdout = StringIO.new
26
26
  $stderr = @stderr = StringIO.new
27
27
  @status = yield
28
28
  ensure
29
- $stdout = real_stdout
30
- $stderr = real_stderr
29
+ $stdout = $real_stdout
30
+ $stderr = $real_stderr
31
31
  end
32
32
  end
@@ -0,0 +1,15 @@
1
+ module ConfigHelper
2
+ def write_token_file(config={})
3
+ File.open(File.expand_path(tokens_file_path), 'w') do |f|
4
+ f.puts YAML.dump(
5
+ { "https://api.some-domain.com" =>
6
+ {
7
+ :version => 2,
8
+ :token => 'bearer token',
9
+ :refresh_token => nil
10
+ }.merge(config)
11
+ }
12
+ )
13
+ end
14
+ end
15
+ end
@@ -10,7 +10,7 @@ module ConsoleAppSpeckerMatchers
10
10
  end
11
11
 
12
12
  def matches?(runner)
13
- raise InvalidInputError unless runner.is_a?(SpeckerRunner)
13
+ raise InvalidInputError unless runner.respond_to?(:expect)
14
14
  expected = runner.expect(@expected_output, @timeout)
15
15
  @full_output = runner.output
16
16
  !!expected
@@ -32,7 +32,7 @@ module ConsoleAppSpeckerMatchers
32
32
  end
33
33
 
34
34
  def matches?(runner)
35
- raise InvalidInputError unless runner.is_a?(SpeckerRunner)
35
+ raise InvalidInputError unless runner.respond_to?(:exit_code)
36
36
 
37
37
  begin
38
38
  Timeout.timeout(5) do
@@ -0,0 +1,42 @@
1
+ module FakeHomeDir
2
+ def self.included(klass)
3
+ super
4
+ klass.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def use_fake_home_dir(&block)
9
+ around do |example|
10
+ dir = instance_exec(&block)
11
+ with_fake_home_dir(dir) do
12
+ example.call
13
+ end
14
+ end
15
+ end
16
+
17
+ def stub_home_dir_with(folder_name)
18
+ around do |example|
19
+ tmp_root = Dir.tmpdir
20
+ FileUtils.cp_r(File.expand_path("#{SPEC_ROOT}/fixtures/fake_home_dirs/#{folder_name}"), tmp_root)
21
+ fake_home_dir = "#{tmp_root}/#{folder_name}"
22
+ begin
23
+ with_fake_home_dir(fake_home_dir) do
24
+ example.call
25
+ end
26
+ ensure
27
+ FileUtils.rm_rf fake_home_dir
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ def with_fake_home_dir(dir, &block)
34
+ original_home_dir = ENV['HOME']
35
+ ENV['HOME'] = dir
36
+ begin
37
+ block.call
38
+ ensure
39
+ ENV['HOME'] = original_home_dir
40
+ end
41
+ end
42
+ end
@@ -1,4 +1,4 @@
1
- module VMC::TestSupport::InteractHelper
1
+ module InteractHelper
2
2
  def stub_ask(*args, &block)
3
3
  a_stub = nil
4
4
  any_instance_of VMC::CLI do |interactive|
@@ -0,0 +1,40 @@
1
+ shared_examples_for "an error that's obvious to the user" do |options|
2
+ message = options[:with_message]
3
+
4
+ it "prints the message" do
5
+ subject
6
+ expect(stderr.string).to include message
7
+ end
8
+
9
+ it "sets the exit code to 1" do
10
+ mock(context).exit_status(1)
11
+ subject
12
+ end
13
+
14
+ it "does not mention ~/.vmc/crash" do
15
+ subject
16
+ expect(stderr.string).to_not include VMC::CRASH_FILE
17
+ end
18
+ end
19
+
20
+ shared_examples_for "an error that gets passed through" do |options|
21
+ before do
22
+ described_class.class_eval do
23
+ alias_method :wrap_errors_original, :wrap_errors
24
+ def wrap_errors
25
+ yield
26
+ end
27
+ end
28
+ end
29
+
30
+ after do
31
+ described_class.class_eval do
32
+ remove_method :wrap_errors
33
+ alias_method :wrap_errors, :wrap_errors_original
34
+ end
35
+ end
36
+
37
+ it "reraises the error" do
38
+ expect { subject }.to raise_error(options[:with_exception], options[:with_message])
39
+ end
40
+ end
@@ -1,18 +1,15 @@
1
- require "expect"
2
1
  require "pty"
3
2
 
4
3
  class SpeckerRunner
5
- attr_reader :output
6
-
7
4
  def initialize(*args)
8
- @output = ""
9
-
10
5
  @stdout, slave = PTY.open
11
6
  system("stty raw", :in => slave)
12
7
  read, @stdin = IO.pipe
13
8
 
14
9
  @pid = spawn(*(args.push(:in => read, :out => slave, :err => slave)))
15
10
 
11
+ @expector = TrackingExpector.new(@stdout, ENV["DEBUG_BACON"])
12
+
16
13
  yield self
17
14
  end
18
15
 
@@ -21,7 +18,7 @@ class SpeckerRunner
21
18
  when Hash
22
19
  expect_branches(matcher, timeout)
23
20
  else
24
- tracking_expect(matcher, timeout)
21
+ @expector.expect(matcher, timeout)
25
22
  end
26
23
  end
27
24
 
@@ -50,11 +47,23 @@ class SpeckerRunner
50
47
  !!Process.getpgid(@pid)
51
48
  end
52
49
 
50
+ def output
51
+ @expector.output
52
+ end
53
+
54
+ def debug
55
+ @expector.debug
56
+ end
57
+
58
+ def debug=(x)
59
+ @expector.debug = x
60
+ end
61
+
53
62
  private
54
63
 
55
64
  def expect_branches(branches, timeout)
56
65
  branch_names = /#{branches.keys.collect { |k| Regexp.quote(k) }.join("|")}/
57
- expected = @stdout.expect(branch_names, timeout)
66
+ expected = @expector.expect(branch_names, timeout)
58
67
  return unless expected
59
68
 
60
69
  data = expected.first.match(/(#{branch_names})$/)
@@ -67,57 +76,4 @@ class SpeckerRunner
67
76
  rescue NoMethodError
68
77
  status
69
78
  end
70
-
71
- def tracking_expect(pattern, timeout)
72
- buffer = ''
73
-
74
- case pattern
75
- when String
76
- pattern = Regexp.new(Regexp.quote(pattern))
77
- when Regexp
78
- else
79
- raise TypeError, "unsupported pattern class: #{pattern.class}"
80
- end
81
-
82
- result = nil
83
- position = 0
84
- @unused ||= ""
85
-
86
- while true
87
- if !@unused.empty?
88
- c = @unused.slice!(0).chr
89
- elsif !IO.select([@stdout], nil, nil, timeout) || @stdout.eof?
90
- @unused = buffer
91
- break
92
- else
93
- c = @stdout.getc.chr
94
- end
95
-
96
- # wear your flip flops
97
- unless (c == "\e") .. (c == "m")
98
- if c == "\b"
99
- if position > 0 && buffer[position - 1] && buffer[position - 1].chr != "\n"
100
- position -= 1
101
- end
102
- else
103
- if buffer.size > position
104
- buffer[position] = c
105
- else
106
- buffer << c
107
- end
108
-
109
- position += 1
110
- end
111
- end
112
-
113
- if matches = pattern.match(buffer)
114
- result = [buffer, *matches.to_a[1..-1]]
115
- break
116
- end
117
- end
118
-
119
- @output << buffer
120
-
121
- result
122
- end
123
- end
79
+ end
@@ -0,0 +1,71 @@
1
+ class TrackingExpector
2
+ attr_reader :output
3
+
4
+ def initialize(out, debug = false)
5
+ @out = out
6
+ @debug = debug
7
+ @unused = ""
8
+ @output = ""
9
+ end
10
+
11
+ def expect(pattern, timeout = 5)
12
+ buffer = ''
13
+
14
+ case pattern
15
+ when String
16
+ pattern = Regexp.new(Regexp.quote(pattern))
17
+ when Regexp
18
+ else
19
+ raise TypeError, "unsupported pattern class: #{pattern.class}"
20
+ end
21
+
22
+ result = nil
23
+ position = 0
24
+ @unused ||= ""
25
+
26
+ while true
27
+ if !@unused.empty?
28
+ c = @unused.slice!(0).chr
29
+ elsif output_ended?(timeout)
30
+ @unused = buffer
31
+ break
32
+ else
33
+ c = @out.getc.chr
34
+ end
35
+
36
+ STDOUT.putc c if @debug
37
+
38
+ # wear your flip flops
39
+ unless (c == "\e") .. (c == "m")
40
+ if c == "\b"
41
+ if position > 0 && buffer[position - 1] && buffer[position - 1].chr != "\n"
42
+ position -= 1
43
+ end
44
+ else
45
+ if buffer.size > position
46
+ buffer[position] = c
47
+ else
48
+ buffer << c
49
+ end
50
+
51
+ position += 1
52
+ end
53
+ end
54
+
55
+ if matches = pattern.match(buffer)
56
+ result = [buffer, *matches.to_a[1..-1]]
57
+ break
58
+ end
59
+ end
60
+
61
+ @output << buffer
62
+
63
+ result
64
+ end
65
+
66
+ private
67
+
68
+ def output_ended?(timeout)
69
+ (@out.is_a?(IO) && !IO.select([@out], nil, nil, timeout)) || @out.eof?
70
+ end
71
+ end
@@ -3,18 +3,18 @@ require 'stringio'
3
3
 
4
4
  describe VMC::App::Stats do
5
5
  let(:global) { { :color => false } }
6
- let(:inputs) { {:app => apps[0]} }
6
+ let(:inputs) { { :app => apps[0] } }
7
7
  let(:given) { {} }
8
8
  let(:client) { fake_client(:apps => apps) }
9
9
  let(:apps) { [fake(:app, :name => "basic_app")] }
10
- let(:time) { Time.local(2012,11,1,2,30)}
10
+ let(:time) { Time.local(2012, 11, 1, 2, 30) }
11
11
 
12
12
  before do
13
13
  any_instance_of(VMC::CLI) do |cli|
14
14
  stub(cli).client { client }
15
15
  stub(cli).precondition { nil }
16
16
  end
17
- stub(client).base.stub!.instances(anything) do
17
+ stub(client.base).instances(anything) do
18
18
  {
19
19
  "12" => {:state => "STOPPED", :since => time.to_i, :debug_ip => "foo", :debug_port => "bar", :console_ip => "baz", :console_port => "qux"},
20
20
  "1" => {:state => "STOPPED", :since => time.to_i, :debug_ip => "foo", :debug_port => "bar", :console_ip => "baz", :console_port => "qux"},
@@ -0,0 +1,201 @@
1
+ require "spec_helper"
2
+ require "webmock/rspec"
3
+
4
+ describe VMC::App::Start do
5
+ include ConsoleAppSpeckerMatchers
6
+
7
+ let(:client) { fake_client :apps => [app] }
8
+ let(:app) { fake :app }
9
+
10
+ def output
11
+ stdout.rewind
12
+ TrackingExpector.new(stdout)
13
+ end
14
+
15
+ def error_output
16
+ stderr.rewind
17
+ TrackingExpector.new(stderr)
18
+ end
19
+
20
+ before do
21
+ any_instance_of described_class do |cli|
22
+ stub(cli).precondition
23
+ stub(cli).client { client }
24
+ end
25
+ end
26
+
27
+ before(:all) do
28
+ described_class.class_eval do
29
+ def wrap_errors
30
+ yield
31
+ end
32
+ end
33
+ end
34
+
35
+ after(:all) do
36
+ described_class.class_eval do
37
+ remove_method :wrap_errors
38
+ end
39
+ end
40
+
41
+ subject { vmc %W[start #{app.name} --no-quiet] }
42
+
43
+ context "with an app that's already started" do
44
+ let(:app) { fake :app, :state => "STARTED" }
45
+
46
+ it "skips starting the application" do
47
+ dont_allow(app).start!
48
+ subject
49
+ end
50
+
51
+ it "says the app is already started" do
52
+ subject
53
+ expect(error_output).to say("Application #{app.name} is already started.")
54
+ end
55
+ end
56
+
57
+ context "with an app that's NOT already started" do
58
+ def self.it_says_application_is_starting
59
+ it "says that it's starting the application" do
60
+ subject
61
+ expect(output).to say("Starting #{app.name}... OK")
62
+ end
63
+ end
64
+
65
+ def self.it_prints_log_progress
66
+ it "prints out the log progress" do
67
+ subject
68
+ expect(output).to say(log_text)
69
+ end
70
+ end
71
+
72
+ def self.it_does_not_print_log_progress
73
+ it "does not print the log progress" do
74
+ subject
75
+ expect(output).to_not say(log_text)
76
+ end
77
+ end
78
+
79
+ def self.it_waits_for_application_to_become_healthy
80
+ describe "waits for application to become healthy" do
81
+ let(:app) { fake :app, :total_instances => 2 }
82
+
83
+ def after_sleep
84
+ any_instance_of described_class do |cli|
85
+ stub(cli).sleep { yield }
86
+ end
87
+ end
88
+
89
+ before do
90
+ stub(app).instances do
91
+ [ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "DOWN"),
92
+ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "RUNNING")
93
+ ]
94
+ end
95
+
96
+ after_sleep do
97
+ stub(app).instances { final_instances }
98
+ end
99
+ end
100
+
101
+ context "when all instances become running" do
102
+ let(:final_instances) do
103
+ [ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "RUNNING"),
104
+ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "RUNNING")
105
+ ]
106
+ end
107
+
108
+ it "says app is started" do
109
+ subject
110
+ expect(output).to say("Checking #{app.name}...")
111
+ expect(output).to say("1 running, 1 down")
112
+ expect(output).to say("2 running")
113
+ end
114
+ end
115
+
116
+ context "when any instance becomes flapping" do
117
+ let(:final_instances) do
118
+ [ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "FLAPPING"),
119
+ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "STARTING")
120
+ ]
121
+ end
122
+
123
+ it "says app failed to start" do
124
+ subject
125
+ expect(output).to say("Checking #{app.name}...")
126
+ expect(output).to say("1 running, 1 down")
127
+ expect(output).to say("1 starting, 1 flapping")
128
+ expect(error_output).to say("Application failed to start")
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ before do
135
+ stub(app).invalidate!
136
+ stub(app).instances do
137
+ [ CFoundry::V2::App::Instance.new(nil, nil, nil, :state => "RUNNING") ]
138
+ end
139
+
140
+ stub(app).start!(true) do |_, blk|
141
+ app.state = "STARTED"
142
+ blk.call(log_url)
143
+ end
144
+ end
145
+
146
+ context "when progress log url is provided" do
147
+ let(:log_url) { "http://example.com/my-app-log" }
148
+ let(:log_text) { "Staging complete!" }
149
+
150
+ context "and progress log url is not available immediately" do
151
+ before do
152
+ stub_request(:get, "#{log_url}&tail&tail_offset=0").to_return(
153
+ :status => 404, :body => "")
154
+ end
155
+
156
+ it_says_application_is_starting
157
+ it_does_not_print_log_progress
158
+ it_waits_for_application_to_become_healthy
159
+ end
160
+
161
+ context "and progress log url becomes unavailable after some time" do
162
+ before do
163
+ stub_request(:get, "#{log_url}&tail&tail_offset=0").to_return(
164
+ :status => 200, :body => log_text[0...5])
165
+ stub_request(:get, "#{log_url}&tail&tail_offset=5").to_return(
166
+ :status => 200, :body => log_text[5..-1])
167
+ stub_request(:get, "#{log_url}&tail&tail_offset=#{log_text.size}").to_return(
168
+ :status => 404, :body => "")
169
+ end
170
+
171
+ it_says_application_is_starting
172
+ it_prints_log_progress
173
+ it_waits_for_application_to_become_healthy
174
+ end
175
+
176
+ context "and a request times out" do
177
+ before do
178
+ stub_request(:get, "#{log_url}&tail&tail_offset=0").to_return(
179
+ :should_timeout => true)
180
+ stub_request(:get, "#{log_url}&tail&tail_offset=0").to_return(
181
+ :status => 200, :body => log_text)
182
+ stub_request(:get, "#{log_url}&tail&tail_offset=#{log_text.size}").to_return(
183
+ :status => 404, :body => "")
184
+ end
185
+
186
+ it_says_application_is_starting
187
+ it_prints_log_progress
188
+ it_waits_for_application_to_become_healthy
189
+ end
190
+ end
191
+
192
+ context "when progress log url is not provided" do
193
+ let(:log_url) { nil }
194
+ let(:log_text) { "Staging complete!" }
195
+
196
+ it_says_application_is_starting
197
+ it_does_not_print_log_progress
198
+ it_waits_for_application_to_become_healthy
199
+ end
200
+ end
201
+ end