vmc 0.5.0.rc1 → 0.5.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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