einhorn 0.8.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Changes.md +10 -0
- data/{LICENSE → LICENSE.txt} +0 -0
- data/README.md +5 -36
- data/einhorn.gemspec +23 -21
- data/example/pool_worker.rb +2 -2
- data/example/thin_example +8 -8
- data/example/time_server +5 -5
- data/lib/einhorn/client.rb +8 -8
- data/lib/einhorn/command/interface.rb +92 -98
- data/lib/einhorn/command.rb +76 -85
- data/lib/einhorn/compat.rb +7 -7
- data/lib/einhorn/event/abstract_text_descriptor.rb +31 -35
- data/lib/einhorn/event/ack_timer.rb +2 -2
- data/lib/einhorn/event/command_server.rb +7 -9
- data/lib/einhorn/event/connection.rb +1 -3
- data/lib/einhorn/event/loop_breaker.rb +2 -1
- data/lib/einhorn/event/persistent.rb +2 -2
- data/lib/einhorn/event/timer.rb +4 -4
- data/lib/einhorn/event.rb +19 -19
- data/lib/einhorn/prctl.rb +2 -2
- data/lib/einhorn/prctl_linux.rb +13 -14
- data/lib/einhorn/safe_yaml.rb +17 -0
- data/lib/einhorn/version.rb +1 -1
- data/lib/einhorn/worker.rb +26 -30
- data/lib/einhorn/worker_pool.rb +9 -9
- data/lib/einhorn.rb +120 -125
- metadata +33 -117
- data/.gitignore +0 -17
- data/.travis.yml +0 -10
- data/CONTRIBUTORS +0 -6
- data/Gemfile +0 -11
- data/History.txt +0 -4
- data/README.md.in +0 -94
- data/Rakefile +0 -27
- data/test/_lib.rb +0 -12
- data/test/integration/_lib/fixtures/env_printer/env_printer.rb +0 -26
- data/test/integration/_lib/fixtures/exit_during_upgrade/exiting_server.rb +0 -23
- data/test/integration/_lib/fixtures/exit_during_upgrade/upgrade_reexec.rb +0 -6
- data/test/integration/_lib/fixtures/pdeathsig_printer/pdeathsig_printer.rb +0 -29
- data/test/integration/_lib/fixtures/signal_timeout/sleepy_server.rb +0 -23
- data/test/integration/_lib/fixtures/upgrade_project/upgrading_server.rb +0 -24
- data/test/integration/_lib/helpers/einhorn_helpers.rb +0 -148
- data/test/integration/_lib/helpers.rb +0 -4
- data/test/integration/_lib.rb +0 -6
- data/test/integration/pdeathsig.rb +0 -26
- data/test/integration/startup.rb +0 -31
- data/test/integration/upgrading.rb +0 -204
- data/test/unit/_lib/bad_worker.rb +0 -7
- data/test/unit/_lib/sleep_worker.rb +0 -5
- data/test/unit/einhorn/client.rb +0 -88
- data/test/unit/einhorn/command/interface.rb +0 -49
- data/test/unit/einhorn/command.rb +0 -135
- data/test/unit/einhorn/event.rb +0 -89
- data/test/unit/einhorn/worker_pool.rb +0 -39
- data/test/unit/einhorn.rb +0 -96
data/.travis.yml
DELETED
data/CONTRIBUTORS
DELETED
data/Gemfile
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# Execute bundler hook if present
|
2
|
-
['~/.', '/etc/'].any? do |file|
|
3
|
-
File.lstat(path = File.expand_path(file + 'bundle-gemfile-hook')) rescue next
|
4
|
-
eval(File.read(path), binding, path); break true
|
5
|
-
end || source('https://rubygems.org/')
|
6
|
-
|
7
|
-
# Only needed for examples
|
8
|
-
gem 'thin-attach_socket'
|
9
|
-
|
10
|
-
# Specify your gem's dependencies in einhorn.gemspec
|
11
|
-
gemspec
|
data/History.txt
DELETED
data/README.md.in
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
# Einhorn: the language-independent shared socket manager
|
2
|
-
|
3
|
-
![Einhorn](https://stripe.com/img/blog/posts/meet-einhorn/einhorn.png)
|
4
|
-
|
5
|
-
Let's say you have a server process which processes one request at a
|
6
|
-
time. Your site is becoming increasingly popular, and this one process
|
7
|
-
is no longer able to handle all of your inbound connections. However,
|
8
|
-
you notice that your box's load number is low.
|
9
|
-
|
10
|
-
So you start thinking about how to handle more requests. You could
|
11
|
-
rewrite your server to use threads, but threads are a pain to program
|
12
|
-
against (and maybe you're writing in Python or Ruby where you don't
|
13
|
-
have true threads anyway). You could rewrite your server to be
|
14
|
-
event-driven, but that'd require a ton of effort, and it wouldn't help
|
15
|
-
you go beyond one core. So instead, you decide to just run multiple
|
16
|
-
copies of your server process.
|
17
|
-
|
18
|
-
Enter Einhorn. Einhorn makes it easy to run (and keep alive) multiple
|
19
|
-
copies of a single long-lived process. If that process is a server
|
20
|
-
listening on some socket, Einhorn will open the socket in the master
|
21
|
-
process so that it's shared among the workers.
|
22
|
-
|
23
|
-
Einhorn is designed to be compatible with arbitrary languages and
|
24
|
-
frameworks, requiring minimal modification of your
|
25
|
-
application. Einhorn is simple to configure and run.
|
26
|
-
|
27
|
-
## Installation
|
28
|
-
|
29
|
-
Install from Rubygems as:
|
30
|
-
|
31
|
-
$ gem install einhorn
|
32
|
-
|
33
|
-
Or build from source by:
|
34
|
-
|
35
|
-
$ gem build einhorn.gemspec
|
36
|
-
|
37
|
-
And then install the built gem.
|
38
|
-
|
39
|
-
[[usage]]
|
40
|
-
|
41
|
-
## Contributing
|
42
|
-
|
43
|
-
Contributions are definitely welcome. To contribute, just follow the
|
44
|
-
usual workflow:
|
45
|
-
|
46
|
-
1. Fork Einhorn
|
47
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
48
|
-
3. Commit your changes (`git commit -am 'Added some feature'`)
|
49
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
50
|
-
5. Create new Github pull request
|
51
|
-
|
52
|
-
## History
|
53
|
-
|
54
|
-
Einhorn came about when Stripe was investigating seamless code
|
55
|
-
upgrading solutions for our API worker processes. We really liked the
|
56
|
-
process model of [Unicorn](http://unicorn.bogomips.org/), but didn't
|
57
|
-
want to use its HTTP functionality. So Einhorn was born, providing the
|
58
|
-
master process functionality of Unicorn (and similar preforking
|
59
|
-
servers) to a wider array of applications.
|
60
|
-
|
61
|
-
See https://stripe.com/blog/meet-einhorn for more background.
|
62
|
-
|
63
|
-
Stripe currently uses Einhorn in production for a number of
|
64
|
-
services. You can use Conrad Irwin's thin-attach_socket gem along with
|
65
|
-
EventMachine-LE to support file-descriptor passing. Check out
|
66
|
-
`example/thin_example` for an example of running Thin under Einhorn.
|
67
|
-
|
68
|
-
## Compatibility
|
69
|
-
|
70
|
-
Einhorn runs in Ruby 2.0, 2.1, and 2.2
|
71
|
-
|
72
|
-
The following libraries ease integration with Einhorn with languages other than
|
73
|
-
Ruby:
|
74
|
-
|
75
|
-
- **[go-einhorn](https://github.com/stripe/go-einhorn)**: Stripe's own library
|
76
|
-
for *talking* to an einhorn master (doesn't wrap socket code).
|
77
|
-
- **[goji](https://github.com/zenazn/goji/)**: Go (golang) server framework. The
|
78
|
-
[`bind`](https://godoc.org/github.com/zenazn/goji/bind) and
|
79
|
-
[`graceful`](https://godoc.org/github.com/zenazn/goji/graceful)
|
80
|
-
packages provide helpers and HTTP/TCP connection wrappers for Einhorn
|
81
|
-
integration.
|
82
|
-
- **[github.com/CHH/einhorn](https://github.com/CHH/einhorn)**: PHP library
|
83
|
-
- **[thin-attach\_socket](https://github.com/ConradIrwin/thin-attach_socket)**:
|
84
|
-
run `thin` behind Einhorn
|
85
|
-
- **[baseplate](https://reddit.github.io/baseplate/cli/serve.html)**: a
|
86
|
-
collection of Python helpers and libraries, with support for running behind
|
87
|
-
Einhorn
|
88
|
-
|
89
|
-
*NB: this list should not imply any official endorsement or vetting!*
|
90
|
-
|
91
|
-
## About
|
92
|
-
|
93
|
-
Einhorn is a project of [Stripe](https://stripe.com), led by [Carl Jackson](https://github.com/zenazn). Feel free to get in touch at
|
94
|
-
info@stripe.com.
|
data/Rakefile
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
|
-
require 'bundler/gem_tasks'
|
3
|
-
require 'rake/testtask'
|
4
|
-
|
5
|
-
desc 'Rebuild the README with the latest usage from einhorn'
|
6
|
-
task :readme do
|
7
|
-
Dir.chdir(File.dirname(__FILE__))
|
8
|
-
readme = File.read('README.md.in')
|
9
|
-
usage = `bin/einhorn -h`
|
10
|
-
readme.gsub!('[[usage]]', usage)
|
11
|
-
File.open('README.md', 'w') {|f| f.write(readme)}
|
12
|
-
end
|
13
|
-
|
14
|
-
task :default => :test do
|
15
|
-
end
|
16
|
-
require 'bundler/setup'
|
17
|
-
require 'chalk-rake/gem_tasks'
|
18
|
-
require 'rake/testtask'
|
19
|
-
|
20
|
-
Rake::TestTask.new do |t|
|
21
|
-
t.libs = ['lib']
|
22
|
-
# t.warning = true
|
23
|
-
t.verbose = true
|
24
|
-
t.test_files = FileList['test/**/*.rb'].reject do |file|
|
25
|
-
file.end_with?('_lib.rb') || file.include?('/_lib/')
|
26
|
-
end
|
27
|
-
end
|
data/test/_lib.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'socket'
|
3
|
-
require 'einhorn/worker'
|
4
|
-
|
5
|
-
def einhorn_main
|
6
|
-
$stderr.puts "Worker starting up!"
|
7
|
-
serv = Socket.for_fd(ENV['EINHORN_FD_0'].to_i)
|
8
|
-
$stderr.puts "Worker has a socket"
|
9
|
-
Einhorn::Worker.ack!
|
10
|
-
$stderr.puts "Worker sent ack to einhorn"
|
11
|
-
$stdout.puts "Environment from #{Process.pid} is: #{ENV.inspect}"
|
12
|
-
while true
|
13
|
-
s, addrinfo = serv.accept
|
14
|
-
$stderr.puts "Worker got a socket!"
|
15
|
-
output = ""
|
16
|
-
ARGV.each do |variable_to_write|
|
17
|
-
output += ENV[variable_to_write].to_s
|
18
|
-
end
|
19
|
-
s.write(output)
|
20
|
-
s.flush
|
21
|
-
s.close
|
22
|
-
$stderr.puts "Worker closed its socket"
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
einhorn_main if $0 == __FILE__
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'socket'
|
3
|
-
require 'einhorn/worker'
|
4
|
-
|
5
|
-
def einhorn_main
|
6
|
-
serv = Socket.for_fd(Einhorn::Worker.socket!)
|
7
|
-
Einhorn::Worker.ack!
|
8
|
-
Einhorn::Worker.ping!("id-1")
|
9
|
-
|
10
|
-
Signal.trap('USR2') do
|
11
|
-
sleep 3
|
12
|
-
exit!
|
13
|
-
end
|
14
|
-
|
15
|
-
while true
|
16
|
-
s, _ = serv.accept
|
17
|
-
s.write($$)
|
18
|
-
s.flush
|
19
|
-
s.close
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
einhorn_main if $0 == __FILE__
|
@@ -1,29 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'socket'
|
3
|
-
require 'einhorn/worker'
|
4
|
-
require 'einhorn/prctl'
|
5
|
-
|
6
|
-
def einhorn_main
|
7
|
-
serv = Socket.for_fd(Einhorn::Worker.socket!)
|
8
|
-
|
9
|
-
Signal.trap("USR2") { exit }
|
10
|
-
|
11
|
-
begin
|
12
|
-
output = Einhorn::Prctl.get_pdeathsig
|
13
|
-
if output == nil then
|
14
|
-
output = "nil"
|
15
|
-
end
|
16
|
-
rescue NotImplementedError
|
17
|
-
output = "not implemented"
|
18
|
-
end
|
19
|
-
|
20
|
-
Einhorn::Worker.ack!
|
21
|
-
while true
|
22
|
-
s, _ = serv.accept
|
23
|
-
s.write(output)
|
24
|
-
s.flush
|
25
|
-
s.close
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
einhorn_main if $0 == __FILE__
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'socket'
|
3
|
-
require 'einhorn/worker'
|
4
|
-
|
5
|
-
def einhorn_main
|
6
|
-
serv = Socket.for_fd(Einhorn::Worker.socket!)
|
7
|
-
Einhorn::Worker.ack!
|
8
|
-
Einhorn::Worker.ping!("id-1")
|
9
|
-
|
10
|
-
Signal.trap('USR2') do
|
11
|
-
sleep ENV.fetch("TRAP_SLEEP").to_i
|
12
|
-
exit
|
13
|
-
end
|
14
|
-
|
15
|
-
while true
|
16
|
-
s, _ = serv.accept
|
17
|
-
s.write($$)
|
18
|
-
s.flush
|
19
|
-
s.close
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
einhorn_main if $0 == __FILE__
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'bundler/setup'
|
2
|
-
require 'socket'
|
3
|
-
require 'einhorn/worker'
|
4
|
-
|
5
|
-
def einhorn_main
|
6
|
-
version = File.read(File.join(File.dirname(__FILE__), "version"))
|
7
|
-
$stderr.puts "Worker starting up!"
|
8
|
-
serv = Socket.for_fd(ENV['EINHORN_FD_0'].to_i)
|
9
|
-
$stderr.puts "Worker has a socket"
|
10
|
-
Einhorn::Worker.ack!
|
11
|
-
$stderr.puts "Worker sent ack to einhorn"
|
12
|
-
Einhorn::Worker.ping!("id-1")
|
13
|
-
$stderr.puts "Worker has sent a ping to einhorn"
|
14
|
-
while true
|
15
|
-
s, addrinfo = serv.accept
|
16
|
-
$stderr.puts "Worker got a socket!"
|
17
|
-
s.write(version)
|
18
|
-
s.flush
|
19
|
-
s.close
|
20
|
-
$stderr.puts "Worker closed its socket"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
einhorn_main if $0 == __FILE__
|
@@ -1,148 +0,0 @@
|
|
1
|
-
require 'subprocess'
|
2
|
-
require 'timeout'
|
3
|
-
require 'tmpdir'
|
4
|
-
|
5
|
-
module Helpers
|
6
|
-
module EinhornHelpers
|
7
|
-
def einhorn_code_dir
|
8
|
-
File.expand_path('../../../../', File.dirname(__FILE__))
|
9
|
-
end
|
10
|
-
|
11
|
-
def default_einhorn_command
|
12
|
-
cmd = ['bundle', 'exec']
|
13
|
-
cmd << '--keep-file-descriptors' if RUBY_VERSION >= '2.0'
|
14
|
-
cmd << File.expand_path('bin/einhorn', einhorn_code_dir)
|
15
|
-
|
16
|
-
cmd
|
17
|
-
end
|
18
|
-
|
19
|
-
def with_running_einhorn(cmdline, options = {})
|
20
|
-
options = options.dup
|
21
|
-
einhorn_command = options.delete(:einhorn_command) { default_einhorn_command }
|
22
|
-
expected_exit_code = options.delete(:expected_exit_code) { nil }
|
23
|
-
output_callback = options.delete(:output_callback) { nil }
|
24
|
-
|
25
|
-
stdout, stderr = "", ""
|
26
|
-
communicator = nil
|
27
|
-
process = Bundler.with_clean_env do
|
28
|
-
default_options = {
|
29
|
-
:stdout => Subprocess::PIPE,
|
30
|
-
:stderr => Subprocess::PIPE,
|
31
|
-
:stdin => '/dev/null',
|
32
|
-
:cwd => einhorn_code_dir
|
33
|
-
}
|
34
|
-
Subprocess::Process.new(Array(einhorn_command) + cmdline, default_options.merge(options))
|
35
|
-
end
|
36
|
-
|
37
|
-
status = nil
|
38
|
-
begin
|
39
|
-
communicator = Thread.new do
|
40
|
-
begin
|
41
|
-
stdout, stderr = process.communicate
|
42
|
-
rescue Errno::ECHILD
|
43
|
-
# It's dead, and we're not getting anything. This is peaceful.
|
44
|
-
end
|
45
|
-
end
|
46
|
-
yield(process) if block_given?
|
47
|
-
rescue
|
48
|
-
unless (status = process.poll) && status.exited?
|
49
|
-
process.terminate
|
50
|
-
end
|
51
|
-
raise
|
52
|
-
ensure
|
53
|
-
unless (status = process.poll) && status.exited?
|
54
|
-
for i in 1..10 do
|
55
|
-
status = process.poll
|
56
|
-
if status && status.exited?
|
57
|
-
break
|
58
|
-
end
|
59
|
-
sleep(1)
|
60
|
-
end
|
61
|
-
unless status && status.exited?
|
62
|
-
$stderr.puts "Could not get Einhorn to quit within 10 seconds, killing it forcefully..."
|
63
|
-
process.send_signal("KILL")
|
64
|
-
status = process.wait
|
65
|
-
end
|
66
|
-
end
|
67
|
-
communicator.join
|
68
|
-
output_callback.call(stdout, stderr) if output_callback
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
def einhornsh(commandline, options = {})
|
73
|
-
Subprocess.check_call(%W{bundle exec #{File.expand_path('bin/einhornsh')}} + commandline,
|
74
|
-
{
|
75
|
-
:stdin => '/dev/null',
|
76
|
-
:stdout => '/dev/null',
|
77
|
-
:stderr => '/dev/null'
|
78
|
-
}.merge(options))
|
79
|
-
end
|
80
|
-
|
81
|
-
def fixture_path(name)
|
82
|
-
File.expand_path(File.join('../fixtures', name), File.dirname(__FILE__))
|
83
|
-
end
|
84
|
-
|
85
|
-
# Creates a new temporary directory with the initial contents from
|
86
|
-
# test/integration/_lib/fixtures/{name} and returns the path to
|
87
|
-
# it. The contents of this directory are temporary and can be
|
88
|
-
# safely overwritten.
|
89
|
-
def prepare_fixture_directory(name)
|
90
|
-
@fixtured_dirs ||= Set.new
|
91
|
-
new_dir = Dir.mktmpdir(name)
|
92
|
-
@fixtured_dirs << new_dir
|
93
|
-
FileUtils.cp_r(File.join(fixture_path(name), '.'), new_dir, :preserve => true)
|
94
|
-
|
95
|
-
new_dir
|
96
|
-
end
|
97
|
-
|
98
|
-
def cleanup_fixtured_directories
|
99
|
-
(@fixtured_dirs || []).each { |dir| FileUtils.rm_rf(dir) }
|
100
|
-
end
|
101
|
-
|
102
|
-
def find_free_port(host='127.0.0.1')
|
103
|
-
open_port = TCPServer.new(host, 0)
|
104
|
-
open_port.addr[1]
|
105
|
-
ensure
|
106
|
-
open_port.close
|
107
|
-
end
|
108
|
-
|
109
|
-
def get_state(client)
|
110
|
-
client.send_command('command' => 'state')
|
111
|
-
YAML.load(client.receive_message['message'])[:state]
|
112
|
-
end
|
113
|
-
|
114
|
-
def wait_for_open_port
|
115
|
-
max_retries = 50
|
116
|
-
begin
|
117
|
-
read_from_port
|
118
|
-
rescue Errno::ECONNREFUSED
|
119
|
-
max_retries -= 1
|
120
|
-
if max_retries <= 0
|
121
|
-
raise
|
122
|
-
else
|
123
|
-
sleep 0.1
|
124
|
-
retry
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
def read_from_port
|
131
|
-
ewouldblock = RUBY_VERSION >= '1.9.0' ? IO::WaitWritable : Errno::EINPROGRESS
|
132
|
-
socket = Socket.new(Socket::PF_INET, Socket::SOCK_STREAM, 0)
|
133
|
-
sockaddr = Socket.pack_sockaddr_in(@port, '127.0.0.1')
|
134
|
-
begin
|
135
|
-
socket.connect_nonblock(sockaddr)
|
136
|
-
rescue ewouldblock
|
137
|
-
IO.select(nil, [socket], [], 5)
|
138
|
-
begin
|
139
|
-
socket.connect_nonblock(sockaddr)
|
140
|
-
rescue Errno::EISCONN
|
141
|
-
end
|
142
|
-
end
|
143
|
-
socket.read.chomp
|
144
|
-
ensure
|
145
|
-
socket.close if socket
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
data/test/integration/_lib.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
require(File.expand_path('_lib', File.dirname(__FILE__)))
|
2
|
-
|
3
|
-
class PdeathsigTest < EinhornIntegrationTestCase
|
4
|
-
include Helpers::EinhornHelpers
|
5
|
-
|
6
|
-
describe 'when run with -k' do
|
7
|
-
before do
|
8
|
-
@dir = prepare_fixture_directory('pdeathsig_printer')
|
9
|
-
@port = find_free_port
|
10
|
-
@server_program = File.join(@dir, 'pdeathsig_printer.rb')
|
11
|
-
@socket_path = File.join(@dir, 'einhorn.sock')
|
12
|
-
end
|
13
|
-
after { cleanup_fixtured_directories }
|
14
|
-
|
15
|
-
it 'sets pdeathsig to USR2 in the child process' do
|
16
|
-
with_running_einhorn(%W{einhorn -m manual -b 127.0.0.1:#{@port} -d #{@socket_path} -k -- ruby #{@server_program}}) do |process|
|
17
|
-
wait_for_open_port
|
18
|
-
output = read_from_port
|
19
|
-
if output != "not implemented" then
|
20
|
-
assert_equal("USR2", output)
|
21
|
-
end
|
22
|
-
process.terminate
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
data/test/integration/startup.rb
DELETED
@@ -1,31 +0,0 @@
|
|
1
|
-
require(File.expand_path('_lib', File.dirname(__FILE__)))
|
2
|
-
|
3
|
-
class StartupTest < EinhornIntegrationTestCase
|
4
|
-
include Helpers::EinhornHelpers
|
5
|
-
|
6
|
-
describe 'when invoked without args' do
|
7
|
-
it 'prints usage and exits with 1' do
|
8
|
-
assert_raises(Subprocess::NonZeroExit) do
|
9
|
-
Subprocess.check_call(default_einhorn_command,
|
10
|
-
:stdout => Subprocess::PIPE,
|
11
|
-
:stderr => Subprocess::PIPE) do |einhorn|
|
12
|
-
stdout, stderr = einhorn.communicate
|
13
|
-
assert_match(/\A## Usage/, stdout)
|
14
|
-
assert_equal(1, einhorn.wait.exitstatus)
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
describe 'when invoked with --upgrade-check' do
|
21
|
-
it 'successfully exits' do
|
22
|
-
Subprocess.check_call(default_einhorn_command + %w[--upgrade-check],
|
23
|
-
:stdout => Subprocess::PIPE,
|
24
|
-
:stderr => Subprocess::PIPE) do |einhorn|
|
25
|
-
stdout, stderr = einhorn.communicate
|
26
|
-
status = einhorn.wait
|
27
|
-
assert_equal(0, status.exitstatus)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|