cf 0.1.5 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,66 @@
1
+ require "spec_helper"
2
+
3
+ if ENV['CF_V2_TEST_USER'] && ENV['CF_V2_TEST_PASSWORD'] && ENV['CF_V2_TEST_TARGET'] && ENV['CF_V2_OTHER_TEST_USER']
4
+ describe 'A user logs in and switches spaces, after a different user has logged in', :ruby19 => true do
5
+ include ConsoleAppSpeckerMatchers
6
+
7
+ let(:target) { ENV['CF_V2_TEST_TARGET'] }
8
+ let(:username) { ENV['CF_V2_TEST_USER'] }
9
+ let(:password) { ENV['CF_V2_TEST_PASSWORD'] }
10
+
11
+ let(:second_username) { ENV['CF_V2_OTHER_TEST_USER'] }
12
+ let(:second_organization) { ENV['CF_V2_OTHER_TEST_ORGANIZATION'] }
13
+ let(:second_space) { ENV['CF_V2_OTHER_TEST_SPACE'] }
14
+ let(:second_password) { ENV['CF_V2_OTHER_TEST_PASSWORD'] || ENV['CF_V2_TEST_PASSWORD'] }
15
+
16
+ before do
17
+ Interact::Progress::Dots.start!
18
+
19
+ run("#{cf_bin} target #{target}") do |runner|
20
+ expect(runner).to say "Setting target"
21
+ expect(runner).to say target
22
+ runner.wait_for_exit
23
+ end
24
+
25
+ run("#{cf_bin} logout") do |runner|
26
+ runner.wait_for_exit
27
+ end
28
+ end
29
+
30
+ after do
31
+ Interact::Progress::Dots.stop!
32
+ end
33
+
34
+ context "when a different user is already logged in" do
35
+ before do
36
+ run("#{cf_bin} login #{username} --password #{password}") do |runner|
37
+ expect(runner).to say "Authenticating... OK"
38
+ expect(runner).to say "Organization>"
39
+ runner.send_keys("pivotal")
40
+
41
+ expect(runner).to say "Switching to organization"
42
+ expect(runner).to say "OK"
43
+
44
+ expect(runner).to say "Space"
45
+ runner.send_keys("1")
46
+
47
+ expect(runner).to say "Switching to space"
48
+ expect(runner).to say "OK"
49
+
50
+ runner.wait_for_exit
51
+ end
52
+ end
53
+
54
+ it "can switch spaces on login" do
55
+ run("#{cf_bin} login #{second_username} --password #{second_password} --organization #{second_organization} --space #{second_space}") do |runner|
56
+ expect(runner).to say "Authenticating... OK"
57
+ expect(runner).to say "Switching to organization #{second_organization}... OK"
58
+ expect(runner).to say "Switching to space #{second_space}... OK"
59
+ runner.wait_for_exit
60
+ end
61
+ end
62
+ end
63
+ end
64
+ else
65
+ $stderr.puts 'Skipping v2 integration specs; please provide $CF_V2_TEST_TARGET, $CF_V2_TEST_USER, $CF_V2_TEST_PASSWORD, and $CF_V2_OTHER_TEST_USER'
66
+ end
@@ -0,0 +1,125 @@
1
+ require "spec_helper"
2
+ require "webmock/rspec"
3
+
4
+ if ENV['CF_V2_TEST_USER'] && ENV['CF_V2_TEST_PASSWORD'] && ENV['CF_V2_TEST_TARGET']
5
+ describe 'A new user tries to use CF against v2', :ruby19 => true do
6
+ include ConsoleAppSpeckerMatchers
7
+ include CF::Interactive
8
+
9
+ let(:target) { ENV['CF_V2_TEST_TARGET'] }
10
+ let(:username) { ENV['CF_V2_TEST_USER'] }
11
+ let(:password) { ENV['CF_V2_TEST_PASSWORD'] }
12
+
13
+ let(:app) do
14
+ fuzz = TRAVIS_BUILD_ID.to_s + Time.new.to_f.to_s.gsub(".", "_")
15
+ "hello-sinatra-#{fuzz}"
16
+ end
17
+
18
+ before do
19
+ FileUtils.rm_rf File.expand_path(CF::CONFIG_DIR)
20
+ WebMock.allow_net_connect!
21
+ Interact::Progress::Dots.start!
22
+ end
23
+
24
+ after do
25
+ cf %W(delete #{app} -f --no-script)
26
+ Interact::Progress::Dots.stop!
27
+ end
28
+
29
+ it 'pushes a simple sinatra app using defaults as much as possible' do
30
+ run("#{cf_bin} target http://#{target}") do |runner|
31
+ expect(runner).to say %r{Setting target to http://#{target}... OK}
32
+ end
33
+
34
+ run("#{cf_bin} login") do |runner|
35
+ expect(runner).to say %r{target: https?://#{target}}
36
+
37
+ expect(runner).to say "Email>"
38
+ runner.send_keys username
39
+
40
+ expect(runner).to say "Password>"
41
+ runner.send_keys password
42
+
43
+ expect(runner).to say "Authenticating... OK"
44
+
45
+ expect(runner).to say(
46
+ "Organization>" => proc {
47
+ runner.send_keys "1"
48
+ expect(runner).to say /Switching to organization .*\.\.\. OK/
49
+ },
50
+ "Switching to organization" => proc {}
51
+ )
52
+
53
+ expect(runner).to say(
54
+ "Space>" => proc {
55
+ runner.send_keys "1"
56
+ expect(runner).to say /Switching to space .*\.\.\. OK/
57
+ },
58
+ "Switching to space" => proc {}
59
+ )
60
+ end
61
+
62
+ run("#{cf_bin} app #{app}") do |runner|
63
+ expect(runner).to say "Unknown app '#{app}'."
64
+ end
65
+
66
+ Dir.chdir("#{SPEC_ROOT}/assets/hello-sinatra") do
67
+ run("#{cf_bin} push") do |runner|
68
+ expect(runner).to say "Name>"
69
+ runner.send_keys app
70
+
71
+ expect(runner).to say "Instances> 1"
72
+ runner.send_keys ""
73
+
74
+ expect(runner).to say "Custom startup command> "
75
+ runner.send_keys "bundle exec ruby main.rb -p $PORT"
76
+
77
+ expect(runner).to say "Memory Limit>"
78
+ runner.send_keys "64M"
79
+
80
+ expect(runner).to say "Creating #{app}... OK"
81
+
82
+ expect(runner).to say "Subdomain> #{app}"
83
+ runner.send_keys ""
84
+
85
+ expect(runner).to say "1:"
86
+ expect(runner).to say "Domain>"
87
+ runner.send_keys "1"
88
+
89
+ expect(runner).to say(/Creating route #{app}\..*\.\.\. OK/)
90
+ expect(runner).to say(/Binding #{app}\..* to #{app}\.\.\. OK/)
91
+
92
+ expect(runner).to say "Create services for application?> n"
93
+ runner.send_keys ""
94
+
95
+ # skip this
96
+ if runner.expect "Bind other services to application?> n", 1
97
+ runner.send_keys ""
98
+ end
99
+
100
+ expect(runner).to say "Save configuration?> n"
101
+ runner.send_keys ""
102
+
103
+ expect(runner).to say "Uploading #{app}... OK"
104
+ expect(runner).to say "Starting #{app}... OK"
105
+
106
+ expect(runner).to say /(Using|Installing) Ruby/i, 10
107
+ expect(runner).to say "Your bundle is complete!", 30
108
+
109
+ expect(runner).to say "Checking #{app}..."
110
+ expect(runner).to say "1/1 instances"
111
+ expect(runner).to say "OK", 30
112
+ end
113
+ end
114
+
115
+ run("#{cf_bin} delete #{app}") do |runner|
116
+ expect(runner).to say "Really delete #{app}?>"
117
+ runner.send_keys "y"
118
+
119
+ expect(runner).to say "Deleting #{app}... OK"
120
+ end
121
+ end
122
+ end
123
+ else
124
+ $stderr.puts 'Skipping v2 integration specs; please provide $CF_V2_TEST_TARGET, $CF_V2_TEST_USER, and $CF_V2_TEST_PASSWORD'
125
+ end
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ if ENV['CF_V2_TEST_TARGET']
4
+ describe 'A new user tries to use CF against v2 production', :ruby19 => true do
5
+ include ConsoleAppSpeckerMatchers
6
+
7
+ let(:target) { ENV['CF_V2_TEST_TARGET'] }
8
+
9
+ before do
10
+ Interact::Progress::Dots.start!
11
+ end
12
+
13
+ after do
14
+ Interact::Progress::Dots.stop!
15
+ end
16
+
17
+ it "can switch targets, even if a target is invalid" do
18
+ run("#{cf_bin} target invalid-target") do |runner|
19
+ expect(runner).to say "Target refused"
20
+ runner.wait_for_exit
21
+ end
22
+
23
+ run("#{cf_bin} target #{target}") do |runner|
24
+ expect(runner).to say "Setting target"
25
+ expect(runner).to say target
26
+ runner.wait_for_exit
27
+ end
28
+ end
29
+ end
30
+ else
31
+ $stderr.puts 'Skipping v2 integration specs; please provide $CF_V2_TEST_TARGET'
32
+ end
@@ -0,0 +1,72 @@
1
+ SPEC_ROOT = File.dirname(__FILE__).freeze
2
+
3
+ require "rspec"
4
+ require "cfoundry"
5
+ require "cfoundry/test_support"
6
+ require "cf"
7
+ require "cf/test_support"
8
+ require "webmock"
9
+ require "ostruct"
10
+ require "fakefs/safe"
11
+
12
+ INTEGRATE_WITH = ENV["INTEGRATE_WITH"] || "default"
13
+ TRAVIS_BUILD_ID = ENV["TRAVIS_BUILD_ID"]
14
+
15
+ OriginalFile = File
16
+
17
+ class FakeFS::File
18
+ def self.fnmatch(*args, &blk)
19
+ OriginalFile.fnmatch(*args, &blk)
20
+ end
21
+ end
22
+
23
+ def cf_bin
24
+ cf = File.expand_path("#{SPEC_ROOT}/../bin/cf.dev")
25
+ if INTEGRATE_WITH != 'default'
26
+ "rvm #{INTEGRATE_WITH}@cf do #{cf}"
27
+ else
28
+ cf
29
+ end
30
+ end
31
+
32
+ Dir[File.expand_path('../support/**/*.rb', __FILE__)].each do |file|
33
+ require file
34
+ end
35
+
36
+ RSpec.configure do |c|
37
+ c.include Fake::FakeMethods
38
+ c.include ConsoleAppSpeckerMatchers
39
+
40
+ c.mock_with :rr
41
+
42
+ if RUBY_VERSION =~ /^1\.8\.\d/
43
+ c.filter_run_excluding :ruby19 => true
44
+ end
45
+
46
+ c.include FakeHomeDir
47
+ c.include CommandHelper
48
+ c.include InteractHelper
49
+ c.include ConfigHelper
50
+
51
+ c.before(:all) do
52
+ WebMock.disable_net_connect!
53
+ end
54
+
55
+ c.before do
56
+ CF::CLI.send(:class_variable_set, :@@client, nil)
57
+ end
58
+ end
59
+
60
+ def name_list(xs)
61
+ if xs.empty?
62
+ "none"
63
+ else
64
+ xs.collect(&:name).join(", ")
65
+ end
66
+ end
67
+
68
+ def run(command)
69
+ SpeckerRunner.new(command) do |runner|
70
+ yield runner
71
+ end
72
+ end
@@ -0,0 +1,81 @@
1
+ def command(klass, &specs)
2
+ describe klass do
3
+ let(:stub_precondition?) { true }
4
+
5
+ before do
6
+ any_instance_of klass do |cli|
7
+ stub(cli).precondition if stub_precondition?
8
+ stub(cli).client { client }
9
+ end
10
+ end
11
+
12
+ before(:all) do
13
+ klass.class_eval do
14
+ def wrap_errors
15
+ yield
16
+ rescue CF::UserError => e
17
+ err e.message
18
+ end
19
+ end
20
+ end
21
+
22
+ after(:all) do
23
+ klass.class_eval do
24
+ remove_method :wrap_errors
25
+ end
26
+ end
27
+
28
+ class_eval(&specs)
29
+ end
30
+ end
31
+
32
+ module CommandHelper
33
+ def cf(argv)
34
+ Mothership.new.exit_status 0
35
+ stub(CF::CLI).exit { |code| code }
36
+ capture_output { CF::CLI.start(argv + ["--debug", "--no-script"]) }
37
+ end
38
+
39
+ def bool_flag(flag)
40
+ "#{'no-' unless send(flag)}#{flag.to_s.gsub('_', '-')}"
41
+ end
42
+
43
+ attr_reader :stdout, :stderr, :stdin, :status
44
+
45
+ def capture_output
46
+ $real_stdout = $stdout
47
+ $real_stderr = $stderr
48
+ $real_stdin = $stdin
49
+ $stdout = @stdout = StringIO.new
50
+ $stderr = @stderr = StringIO.new
51
+ $stdin = @stdin = StringIO.new
52
+ @status = yield
53
+ @stdout.rewind
54
+ @stderr.rewind
55
+ @status
56
+ ensure
57
+ $stdout = $real_stdout
58
+ $stderr = $real_stderr
59
+ $stdin = $real_stdin
60
+ end
61
+
62
+ def output
63
+ @output ||= TrackingExpector.new(stdout)
64
+ end
65
+
66
+ def error_output
67
+ @error_output ||= TrackingExpector.new(stderr)
68
+ end
69
+
70
+ def mock_invoke(*args)
71
+ any_instance_of described_class do |cli|
72
+ mock(cli).invoke *args
73
+ end
74
+ end
75
+
76
+ def dont_allow_invoke(*args)
77
+ any_instance_of described_class do |cli|
78
+ dont_allow(cli).invoke *args
79
+ end
80
+ end
81
+ 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
@@ -0,0 +1,86 @@
1
+ module ConsoleAppSpeckerMatchers
2
+ class InvalidInputError < StandardError; end
3
+
4
+ class ExpectOutputMatcher
5
+ attr_reader :timeout
6
+
7
+ def initialize(expected_output, timeout = 30)
8
+ @expected_output = expected_output
9
+ @timeout = timeout
10
+ end
11
+
12
+ def matches?(runner)
13
+ raise InvalidInputError unless runner.respond_to?(:expect)
14
+ @matched = runner.expect(@expected_output, @timeout)
15
+ @full_output = runner.output
16
+ !!@matched
17
+ end
18
+
19
+ def failure_message
20
+ if @expected_output.is_a?(Hash)
21
+ expected_keys = @expected_output.keys.map{|key| "'#{key}'"}.join(', ')
22
+ "expected one of #{expected_keys} to be printed, but it wasn't. full output:\n#@full_output"
23
+ else
24
+ "expected '#{@expected_output}' to be printed, but it wasn't. full output:\n#@full_output"
25
+ end
26
+ end
27
+
28
+ def negative_failure_message
29
+ if @expected_output.is_a?(Hash)
30
+ match = @matched
31
+ else
32
+ match = @expected_output
33
+ end
34
+
35
+ "expected '#{match}' to not be printed, but it was. full output:\n#@full_output"
36
+ end
37
+ end
38
+
39
+
40
+ class ExitCodeMatcher
41
+ def initialize(expected_code)
42
+ @expected_code = expected_code
43
+ end
44
+
45
+ def matches?(runner)
46
+ raise InvalidInputError unless runner.respond_to?(:exit_code)
47
+
48
+ begin
49
+ Timeout.timeout(5) do
50
+ @actual_code = runner.exit_code
51
+ end
52
+
53
+ @actual_code == @expected_code
54
+ rescue Timeout::Error
55
+ @timed_out = true
56
+ false
57
+ end
58
+ end
59
+
60
+ def failure_message
61
+ if @timed_out
62
+ "expected process to exit with status #@expected_code, but it did not exit within 5 seconds"
63
+ else
64
+ "expected process to exit with status #{@expected_code}, but it exited with status #{@actual_code}"
65
+ end
66
+ end
67
+
68
+ def negative_failure_message
69
+ if @timed_out
70
+ "expected process to exit with status #@expected_code, but it did not exit within 5 seconds"
71
+ else
72
+ "expected process to not exit with status #{@expected_code}, but it did"
73
+ end
74
+ end
75
+ end
76
+
77
+ def say(expected_output, timeout = 30)
78
+ ExpectOutputMatcher.new(expected_output, timeout)
79
+ end
80
+
81
+ def have_exited_with(expected_code)
82
+ ExitCodeMatcher.new(expected_code)
83
+ end
84
+
85
+ alias :exit_with :have_exited_with
86
+ end