zbatery 0.1.1 → 0.2.0
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.
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/bin/zbatery +3 -41
- data/lib/zbatery.rb +21 -1
- data/t/.gitignore +4 -0
- data/t/GNUmakefile +117 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/sha1sum.rb +23 -0
- data/t/bin/unused_listen +40 -0
- data/t/bin/utee +12 -0
- data/t/large-file-response.ru +14 -0
- data/t/my-tap-lib.sh +200 -0
- data/t/sleep.ru +13 -0
- data/t/t0003-reopen-logs.sh +107 -0
- data/t/t0005-large-file-response.sh +83 -0
- data/t/test-lib.sh +155 -0
- data/zbatery.gemspec +2 -1
- metadata +25 -3
data/GIT-VERSION-GEN
CHANGED
data/GNUmakefile
CHANGED
data/bin/zbatery
CHANGED
@@ -112,49 +112,11 @@ end
|
|
112
112
|
config = ARGV[0] || "config.ru"
|
113
113
|
abort "configuration file #{config} not found" unless File.exist?(config)
|
114
114
|
|
115
|
-
|
116
|
-
# parse embedded command-line options in config.ru comments
|
117
|
-
if File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) } =~ /^#\\(.*)/
|
118
|
-
opts.parse! $1.split(/\s+/)
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
require 'pp' if $DEBUG
|
123
|
-
|
124
|
-
app = lambda do ||
|
125
|
-
# require Rack as late as possible in case $LOAD_PATH is modified
|
126
|
-
# in config.ru or command-line
|
127
|
-
inner_app = case config
|
128
|
-
when /\.ru$/
|
129
|
-
raw = File.open(config, "rb") { |fp| fp.sysread(fp.stat.size) }
|
130
|
-
raw.sub!(/^__END__\n.*/, '')
|
131
|
-
eval("Rack::Builder.new {(#{raw}\n)}.to_app", nil, config)
|
132
|
-
else
|
133
|
-
require config
|
134
|
-
Object.const_get(File.basename(config, '.rb').capitalize)
|
135
|
-
end
|
136
|
-
pp({ :inner_app => inner_app }) if $DEBUG
|
137
|
-
case ENV["RACK_ENV"]
|
138
|
-
when "development"
|
139
|
-
Rack::Builder.new do
|
140
|
-
use Rack::CommonLogger, $stderr
|
141
|
-
use Rack::ShowExceptions
|
142
|
-
use Rack::Lint
|
143
|
-
run inner_app
|
144
|
-
end.to_app
|
145
|
-
when "deployment"
|
146
|
-
Rack::Builder.new do
|
147
|
-
use Rack::CommonLogger, $stderr
|
148
|
-
run inner_app
|
149
|
-
end.to_app
|
150
|
-
else
|
151
|
-
inner_app
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
115
|
+
app = Unicorn.builder(config, opts)
|
155
116
|
listeners << "#{host}:#{port}" if set_listener
|
156
117
|
|
157
118
|
if $DEBUG
|
119
|
+
require 'pp'
|
158
120
|
pp({
|
159
121
|
:zbatery_options => options,
|
160
122
|
:app => app,
|
@@ -162,5 +124,5 @@ if $DEBUG
|
|
162
124
|
})
|
163
125
|
end
|
164
126
|
|
165
|
-
Unicorn::Launcher.daemonize! if daemonize
|
127
|
+
Unicorn::Launcher.daemonize!(options) if daemonize
|
166
128
|
Zbatery.run(app, options)
|
data/lib/zbatery.rb
CHANGED
@@ -4,7 +4,7 @@ require 'rainbows'
|
|
4
4
|
module Zbatery
|
5
5
|
|
6
6
|
# current version of Zbatery
|
7
|
-
VERSION = "0.
|
7
|
+
VERSION = "0.2.0"
|
8
8
|
|
9
9
|
class << self
|
10
10
|
|
@@ -60,6 +60,7 @@ module Zbatery
|
|
60
60
|
|
61
61
|
# no-op
|
62
62
|
def maintain_worker_count; end
|
63
|
+
def init_self_pipe!; end
|
63
64
|
|
64
65
|
# can't just do a graceful exit if reopening logs fails, so we just
|
65
66
|
# continue on...
|
@@ -71,6 +72,10 @@ module Zbatery
|
|
71
72
|
logger.error "failed reopening logs #{e.message}"
|
72
73
|
end
|
73
74
|
|
75
|
+
def trap_deferred(sig)
|
76
|
+
# nothing
|
77
|
+
end
|
78
|
+
|
74
79
|
def join
|
75
80
|
begin
|
76
81
|
trap(:INT) { stop(false) } # Mongrel trapped INT for Win32...
|
@@ -91,6 +96,13 @@ module Zbatery
|
|
91
96
|
rescue => e # hopefully ignores errors on Win32...
|
92
97
|
logger.error "failed to setup signal handler: #{e.message}"
|
93
98
|
end
|
99
|
+
|
100
|
+
if ready_pipe
|
101
|
+
ready_pipe.syswrite($$.to_s)
|
102
|
+
ready_pipe.close rescue nil
|
103
|
+
self.ready_pipe = nil
|
104
|
+
end
|
105
|
+
|
94
106
|
worker = Worker.new(0, DeadIO.new)
|
95
107
|
before_fork.call(self, worker)
|
96
108
|
worker_loop(worker) # runs forever
|
@@ -125,7 +137,15 @@ module Rainbows
|
|
125
137
|
# master == worker in our case
|
126
138
|
def init_worker_process(worker)
|
127
139
|
after_fork.call(self, worker)
|
140
|
+
worker.user(*user) if user.kind_of?(Array) && ! worker.switched
|
128
141
|
build_app! unless preload_app
|
142
|
+
|
143
|
+
# avoid spurious wakeups and blocking-accept() with 1.8 green threads
|
144
|
+
if RUBY_VERSION.to_f < 1.9
|
145
|
+
require "io/nonblock"
|
146
|
+
HttpServer::LISTENERS.each { |l| l.nonblock = true }
|
147
|
+
end
|
148
|
+
|
129
149
|
logger.info "Zbatery #@use worker_connections=#@worker_connections"
|
130
150
|
end
|
131
151
|
end
|
data/t/.gitignore
ADDED
data/t/GNUmakefile
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# we can run tests in parallel with GNU make
|
2
|
+
|
3
|
+
all::
|
4
|
+
|
5
|
+
pid := $(shell echo $$PPID)
|
6
|
+
|
7
|
+
RUBY = ruby
|
8
|
+
zbatery_lib := $(shell cd ../lib && pwd)
|
9
|
+
-include ../local.mk
|
10
|
+
ifeq ($(RUBY_VERSION),)
|
11
|
+
RUBY_VERSION := $(shell $(RUBY) -e 'puts RUBY_VERSION')
|
12
|
+
endif
|
13
|
+
|
14
|
+
ifeq ($(RUBY_VERSION),)
|
15
|
+
$(error unable to detect RUBY_VERSION)
|
16
|
+
endif
|
17
|
+
|
18
|
+
ifeq ($(RUBYLIB),)
|
19
|
+
RUBYLIB := $(zbatery_lib)
|
20
|
+
else
|
21
|
+
RUBYLIB := $(zbatery_lib):$(RUBYLIB)
|
22
|
+
endif
|
23
|
+
export RUBYLIB RUBY_VERSION
|
24
|
+
|
25
|
+
models += ThreadPool
|
26
|
+
models += ThreadSpawn
|
27
|
+
models += Rev
|
28
|
+
models += EventMachine
|
29
|
+
models += NeverBlock
|
30
|
+
models += RevThreadSpawn
|
31
|
+
models += RevThreadPool
|
32
|
+
|
33
|
+
rp := )
|
34
|
+
ONENINE := $(shell case $(RUBY_VERSION) in 1.9.*$(rp) echo true;;esac)
|
35
|
+
ifeq ($(ONENINE),true)
|
36
|
+
models += Revactor
|
37
|
+
models += FiberSpawn
|
38
|
+
models += RevFiberSpawn
|
39
|
+
models += FiberPool
|
40
|
+
endif
|
41
|
+
all_models := $(models) Base
|
42
|
+
|
43
|
+
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
44
|
+
|
45
|
+
MODEL_T := $(foreach m,$(all_models),$(addprefix $(m).,$(T)))
|
46
|
+
$(T): MODELS = $(models)
|
47
|
+
|
48
|
+
# some tests can be run with all models
|
49
|
+
t0000-simple-http.sh: MODELS = $(all_models)
|
50
|
+
t0001-unix-http.sh: MODELS = $(all_models)
|
51
|
+
t0002-graceful.sh: MODELS = $(all_models)
|
52
|
+
t0002-parser-error.sh: MODELS = $(all_models)
|
53
|
+
t0003-reopen-logs.sh: MODELS = $(all_models)
|
54
|
+
|
55
|
+
# recursively run per-model tests
|
56
|
+
# haven't figured out a good way to make make non-recursive here, yet...
|
57
|
+
$(T):
|
58
|
+
$(MAKE) $(foreach m,$(MODELS),$(addprefix $(m).,$@))
|
59
|
+
|
60
|
+
$(all_models):
|
61
|
+
$(MAKE) $(filter $@.%,$(MODEL_T))
|
62
|
+
|
63
|
+
all:: $(T)
|
64
|
+
|
65
|
+
# can't rely on "set -o pipefail" since we don't require bash or ksh93 :<
|
66
|
+
t_pfx = trash/$@-$(RUBY_VERSION)
|
67
|
+
TEST_OPTS =
|
68
|
+
# TRACER = strace -f -o $(t_pfx).strace -s 100000
|
69
|
+
# TRACER = /usr/bin/time -o $(t_pfx).time
|
70
|
+
|
71
|
+
ifdef V
|
72
|
+
ifeq ($(V),2)
|
73
|
+
TEST_OPTS += --trace
|
74
|
+
else
|
75
|
+
TEST_OPTS += --verbose
|
76
|
+
endif
|
77
|
+
endif
|
78
|
+
|
79
|
+
test-bin-$(RUBY_VERSION)/zbatery: ruby_bin = $(shell which $(RUBY))
|
80
|
+
test-bin-$(RUBY_VERSION)/zbatery: ../bin/zbatery
|
81
|
+
mkdir -p $(@D)
|
82
|
+
install -m 755 $^ $@.$(pid)
|
83
|
+
$(RUBY) -i -p -e '$$_.gsub!(%r{^#!.*$$},"#!$(ruby_bin)")' $@.$(pid)
|
84
|
+
mv $@.$(pid) $@
|
85
|
+
|
86
|
+
random_blob:
|
87
|
+
dd if=/dev/urandom bs=1M count=30 of=$@.$(pid)
|
88
|
+
mv $@.$(pid) $@
|
89
|
+
|
90
|
+
$(T): random_blob
|
91
|
+
|
92
|
+
dependencies := socat curl
|
93
|
+
deps := $(addprefix .dep+,$(dependencies))
|
94
|
+
$(deps): dep_bin = $(lastword $(subst +, ,$@))
|
95
|
+
$(deps):
|
96
|
+
@which $(dep_bin) > $@.$(pid) 2>/dev/null || :
|
97
|
+
@test -s $@.$(pid) || \
|
98
|
+
{ echo >&2 "E '$(dep_bin)' not found in PATH=$(PATH)"; exit 1; }
|
99
|
+
@mv $@.$(pid) $@
|
100
|
+
dep: $(deps)
|
101
|
+
|
102
|
+
$(MODEL_T): export model = $(firstword $(subst ., ,$@))
|
103
|
+
$(MODEL_T): script = $(subst $(model).,,$@)
|
104
|
+
$(MODEL_T): trash/.gitignore
|
105
|
+
$(MODEL_T): export RUBY := $(RUBY)
|
106
|
+
$(MODEL_T): export PATH := $(CURDIR)/test-bin-$(RUBY_VERSION):$(PATH)
|
107
|
+
$(MODEL_T): test-bin-$(RUBY_VERSION)/zbatery dep
|
108
|
+
$(TRACER) $(SHELL) $(SH_TEST_OPTS) $(script) $(TEST_OPTS)
|
109
|
+
|
110
|
+
trash/.gitignore:
|
111
|
+
mkdir -p $(@D)
|
112
|
+
echo '*' > $@
|
113
|
+
|
114
|
+
clean:
|
115
|
+
$(RM) -r trash/*.log trash/*.code test-bin-$(RUBY_VERSION)
|
116
|
+
|
117
|
+
.PHONY: $(T) clean
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# simple chunked HTTP PUT request generator (and just that),
|
4
|
+
# it reads stdin and writes to stdout so socat can write to a
|
5
|
+
# UNIX or TCP socket (or to another filter or file) along with
|
6
|
+
# a Content-MD5 trailer.
|
7
|
+
require 'digest/md5'
|
8
|
+
$stdout.sync = $stderr.sync = true
|
9
|
+
$stdout.binmode
|
10
|
+
$stdin.binmode
|
11
|
+
|
12
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
13
|
+
|
14
|
+
if ARGV.grep("--no-headers").empty?
|
15
|
+
$stdout.write(
|
16
|
+
"PUT / HTTP/1.1\r\n" \
|
17
|
+
"Host: example.com\r\n" \
|
18
|
+
"Transfer-Encoding: chunked\r\n" \
|
19
|
+
"Trailer: Content-MD5\r\n" \
|
20
|
+
"\r\n"
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
digest = Digest::MD5.new
|
25
|
+
if buf = $stdin.readpartial(bs)
|
26
|
+
begin
|
27
|
+
digest.update(buf)
|
28
|
+
$stdout.write("%x\r\n" % [ buf.size ])
|
29
|
+
$stdout.write(buf)
|
30
|
+
$stdout.write("\r\n")
|
31
|
+
end while $stdin.read(bs, buf)
|
32
|
+
end
|
33
|
+
|
34
|
+
digest = [ digest.digest ].pack('m').strip
|
35
|
+
$stdout.write("0\r\n")
|
36
|
+
$stdout.write("Content-MD5: #{digest}\r\n\r\n")
|
data/t/bin/sha1sum.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
|
4
|
+
# Reads from stdin and outputs the SHA1 hex digest of the input this is
|
5
|
+
# ONLY used as a last resort, our test code will try to use sha1sum(1),
|
6
|
+
# openssl(1), or gsha1sum(1) before falling back to using this. We try
|
7
|
+
# all options first because we have a strong and healthy distrust of our
|
8
|
+
# Ruby abilities in general, and *especially* when it comes to
|
9
|
+
# understanding (and trusting the implementation of) Ruby 1.9 encoding.
|
10
|
+
|
11
|
+
require 'digest/sha1'
|
12
|
+
$stdout.sync = $stderr.sync = true
|
13
|
+
$stdout.binmode
|
14
|
+
$stdin.binmode
|
15
|
+
bs = 16384
|
16
|
+
digest = Digest::SHA1.new
|
17
|
+
if buf = $stdin.read(bs)
|
18
|
+
begin
|
19
|
+
digest.update(buf)
|
20
|
+
end while $stdin.read(bs, buf)
|
21
|
+
end
|
22
|
+
|
23
|
+
$stdout.syswrite("#{digest.hexdigest}\n")
|
data/t/bin/unused_listen
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# this is to remain compatible with the unused_port function in the
|
4
|
+
# Unicorn test/test_helper.rb file
|
5
|
+
require 'socket'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
default_port = 8080
|
9
|
+
addr = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1'
|
10
|
+
retries = 100
|
11
|
+
base = 5000
|
12
|
+
port = sock = lock_path = nil
|
13
|
+
|
14
|
+
begin
|
15
|
+
begin
|
16
|
+
port = base + rand(32768 - base)
|
17
|
+
while port == default_port
|
18
|
+
port = base + rand(32768 - base)
|
19
|
+
end
|
20
|
+
|
21
|
+
sock = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
|
22
|
+
sock.bind(Socket.pack_sockaddr_in(port, addr))
|
23
|
+
sock.listen(5)
|
24
|
+
rescue Errno::EADDRINUSE, Errno::EACCES
|
25
|
+
sock.close rescue nil
|
26
|
+
retry if (retries -= 1) >= 0
|
27
|
+
end
|
28
|
+
|
29
|
+
# since we'll end up closing the random port we just got, there's a race
|
30
|
+
# condition could allow the random port we just chose to reselect itself
|
31
|
+
# when running tests in parallel with gmake. Create a lock file while
|
32
|
+
# we have the port here to ensure that does not happen.
|
33
|
+
lock_path = "#{Dir::tmpdir}/unicorn_test.#{addr}:#{port}.lock"
|
34
|
+
lock = File.open(lock_path, File::WRONLY|File::CREAT|File::EXCL, 0600)
|
35
|
+
rescue Errno::EEXIST
|
36
|
+
sock.close rescue nil
|
37
|
+
retry
|
38
|
+
end
|
39
|
+
sock.close rescue nil
|
40
|
+
puts %Q(listen=#{addr}:#{port} T_RM_LIST="$T_RM_LIST #{lock_path}")
|
data/t/bin/utee
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: binary -*-
|
3
|
+
# tee(1) as distributed on most(all?) systems is buffered in luserspace
|
4
|
+
# this only does unbuffered writes (with line-buffered input) to make
|
5
|
+
# test output appear in real-time
|
6
|
+
$stdin.binmode
|
7
|
+
$stdout.binmode
|
8
|
+
fp = File.open(ARGV.shift, "wb")
|
9
|
+
$stdin.each_line do |line|
|
10
|
+
fp.syswrite line
|
11
|
+
$stdout.syswrite line
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# lib-large-file-response will stop running if we're not on Linux here
|
2
|
+
use Rack::ContentLength
|
3
|
+
use Rack::ContentType
|
4
|
+
map "/rss" do
|
5
|
+
run lambda { |env|
|
6
|
+
# on Linux, this is in kilobytes
|
7
|
+
GC.start if GC.respond_to?(:start)
|
8
|
+
::File.read("/proc/self/status") =~ /^VmRSS:\s+(\d+)/
|
9
|
+
[ 200, {}, [ ($1.to_i * 1024).to_s ] ]
|
10
|
+
}
|
11
|
+
end
|
12
|
+
map "/" do
|
13
|
+
run Rack::File.new(Dir.pwd)
|
14
|
+
end
|
data/t/my-tap-lib.sh
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Copyright (c) 2009 Eric Wong <normalperson@yhbt.net>
|
3
|
+
#
|
4
|
+
# TAP-producing shell library for POSIX-compliant Bourne shells We do
|
5
|
+
# not _rely_ on Bourne Again features, though we will use "set -o
|
6
|
+
# pipefail" from ksh93 or bash 3 if available
|
7
|
+
#
|
8
|
+
# Only generic, non-project/non-language-specific stuff goes here. We
|
9
|
+
# only have POSIX dependencies for the core tests (without --verbose),
|
10
|
+
# though we'll enable useful non-POSIX things if they're available.
|
11
|
+
#
|
12
|
+
# This test library is intentionally unforgiving, it does not support
|
13
|
+
# skipping tests nor continuing after any failure. Any failures
|
14
|
+
# immediately halt execution as do any references to undefined
|
15
|
+
# variables.
|
16
|
+
#
|
17
|
+
# When --verbose is specified, we always prefix stdout/stderr
|
18
|
+
# output with "#" to avoid confusing TAP consumers. Otherwise
|
19
|
+
# the normal stdout/stderr streams are redirected to /dev/null
|
20
|
+
|
21
|
+
# dup normal stdout(fd=1) and stderr (fd=2) to fd=3 and fd=4 respectively
|
22
|
+
# normal TAP output goes to fd=3, nothing should go to fd=4
|
23
|
+
exec 3>&1 4>&2
|
24
|
+
|
25
|
+
# ensure a sane environment
|
26
|
+
TZ=UTC LC_ALL=C LANG=C
|
27
|
+
export LANG LC_ALL TZ
|
28
|
+
unset CDPATH
|
29
|
+
|
30
|
+
# pipefail is non-POSIX, but very useful in ksh93/bash
|
31
|
+
( set -o pipefail 2>/dev/null ) && set -o pipefail
|
32
|
+
|
33
|
+
SED=${SED-sed}
|
34
|
+
|
35
|
+
# Unlike other test frameworks, we are unforgiving and bail immediately
|
36
|
+
# on any failures. We do this because we're lazy about error handling
|
37
|
+
# and also because we believe anything broken should not be allowed to
|
38
|
+
# propagate throughout the rest of the test
|
39
|
+
set -e
|
40
|
+
set -u
|
41
|
+
|
42
|
+
# name of our test
|
43
|
+
T=${0##*/}
|
44
|
+
|
45
|
+
t_expect_nr=-1
|
46
|
+
t_nr=0
|
47
|
+
t_current=
|
48
|
+
t_complete=false
|
49
|
+
|
50
|
+
# list of files to remove unconditionally on exit
|
51
|
+
T_RM_LIST=
|
52
|
+
|
53
|
+
# list of files to remove only on successful exit
|
54
|
+
T_OK_RM_LIST=
|
55
|
+
|
56
|
+
# emit output to stdout, it'll be parsed by the TAP consumer
|
57
|
+
# so it must be TAP-compliant output
|
58
|
+
t_echo () {
|
59
|
+
echo >&3 "$@"
|
60
|
+
}
|
61
|
+
|
62
|
+
# emits non-parsed information to stdout, it will be prefixed with a '#'
|
63
|
+
# to not throw off TAP consumers
|
64
|
+
t_info () {
|
65
|
+
t_echo '#' "$@"
|
66
|
+
}
|
67
|
+
|
68
|
+
# exit with an error and print a diagnostic
|
69
|
+
die () {
|
70
|
+
echo >&2 "$@"
|
71
|
+
exit 1
|
72
|
+
}
|
73
|
+
|
74
|
+
# our at_exit handler, it'll fire for all exits except SIGKILL (unavoidable)
|
75
|
+
t_at_exit () {
|
76
|
+
code=$?
|
77
|
+
set +e
|
78
|
+
if test $code -eq 0
|
79
|
+
then
|
80
|
+
$t_complete || {
|
81
|
+
t_info "t_done not called"
|
82
|
+
code=1
|
83
|
+
}
|
84
|
+
elif test -n "$t_current"
|
85
|
+
then
|
86
|
+
t_echo "not ok $t_nr - $t_current"
|
87
|
+
fi
|
88
|
+
if test $t_expect_nr -ne -1
|
89
|
+
then
|
90
|
+
test $t_expect_nr -eq $t_nr || {
|
91
|
+
t_info "planned $t_expect_nr tests but ran $t_nr"
|
92
|
+
test $code -ne 0 || code=1
|
93
|
+
}
|
94
|
+
fi
|
95
|
+
$t_complete || {
|
96
|
+
t_info "unexpected test failure"
|
97
|
+
test $code -ne 0 || code=1
|
98
|
+
}
|
99
|
+
rm -f $T_RM_LIST
|
100
|
+
test $code -eq 0 && rm -f $T_OK_RM_LIST
|
101
|
+
set +x
|
102
|
+
exec >&3 2>&4
|
103
|
+
t_close_fds
|
104
|
+
exit $code
|
105
|
+
}
|
106
|
+
|
107
|
+
# close test-specific extra file descriptors
|
108
|
+
t_close_fds () {
|
109
|
+
exec 3>&- 4>&-
|
110
|
+
}
|
111
|
+
|
112
|
+
# call this at the start of your test to specify the number of tests
|
113
|
+
# you plan to run
|
114
|
+
t_plan () {
|
115
|
+
test "$1" -ge 1 || die "must plan at least one test"
|
116
|
+
test $t_expect_nr -eq -1 || die "tried to plan twice in one test"
|
117
|
+
t_expect_nr=$1
|
118
|
+
shift
|
119
|
+
t_echo 1..$t_expect_nr "#" "$@"
|
120
|
+
trap t_at_exit EXIT
|
121
|
+
}
|
122
|
+
|
123
|
+
_t_checkup () {
|
124
|
+
test $t_expect_nr -le 0 && die "no tests planned"
|
125
|
+
test -n "$t_current" && t_echo "ok $t_nr - $t_current"
|
126
|
+
true
|
127
|
+
}
|
128
|
+
|
129
|
+
# finalizes any previously test and starts a new one
|
130
|
+
t_begin () {
|
131
|
+
_t_checkup
|
132
|
+
t_nr=$(( $t_nr + 1 ))
|
133
|
+
t_current="$1"
|
134
|
+
|
135
|
+
# just in case somebody wanted to cheat us:
|
136
|
+
set -e
|
137
|
+
}
|
138
|
+
|
139
|
+
# finalizes the current test without starting a new one
|
140
|
+
t_end () {
|
141
|
+
_t_checkup
|
142
|
+
t_current=
|
143
|
+
}
|
144
|
+
|
145
|
+
# run this to signify the end of your test
|
146
|
+
t_done () {
|
147
|
+
_t_checkup
|
148
|
+
t_current=
|
149
|
+
t_complete=true
|
150
|
+
test $t_expect_nr -eq $t_nr || exit 1
|
151
|
+
exit 0
|
152
|
+
}
|
153
|
+
|
154
|
+
# create and assign named-pipes to variable _names_ passed to this function
|
155
|
+
t_fifos () {
|
156
|
+
for _id in "$@"
|
157
|
+
do
|
158
|
+
_name=$_id
|
159
|
+
_tmp=$(mktemp -t $T.$$.$_id.XXXXXXXX)
|
160
|
+
eval "$_id=$_tmp"
|
161
|
+
rm -f $_tmp
|
162
|
+
mkfifo $_tmp
|
163
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
164
|
+
done
|
165
|
+
}
|
166
|
+
|
167
|
+
t_verbose=false t_trace=false
|
168
|
+
|
169
|
+
while test "$#" -ne 0
|
170
|
+
do
|
171
|
+
arg="$1"
|
172
|
+
shift
|
173
|
+
case $arg in
|
174
|
+
-v|--verbose) t_verbose=true ;;
|
175
|
+
--trace) t_trace=true t_verbose=true ;;
|
176
|
+
*) die "Unknown option: $arg" ;;
|
177
|
+
esac
|
178
|
+
done
|
179
|
+
|
180
|
+
# we always only setup stdout, nothing should end up in the "real" stderr
|
181
|
+
if $t_verbose
|
182
|
+
then
|
183
|
+
if test x"$(which mktemp 2>/dev/null)" = x
|
184
|
+
then
|
185
|
+
die "mktemp(1) not available for --verbose"
|
186
|
+
fi
|
187
|
+
t_fifos t_stdout t_stderr
|
188
|
+
|
189
|
+
(
|
190
|
+
# use a subshell so seds are not waitable
|
191
|
+
$SED -e 's/^/#: /' $t_stdout &
|
192
|
+
$SED -e 's/^/#! /' $t_stderr &
|
193
|
+
) &
|
194
|
+
exec > $t_stdout 2> $t_stderr
|
195
|
+
else
|
196
|
+
exec > /dev/null 2> /dev/null
|
197
|
+
fi
|
198
|
+
|
199
|
+
$t_trace && set -x
|
200
|
+
true
|
data/t/sleep.ru
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
use Rack::ContentLength
|
2
|
+
|
3
|
+
run lambda { |env|
|
4
|
+
/\A100-continue\z/i =~ env['HTTP_EXPECT'] and return [ 100, {}, [] ]
|
5
|
+
|
6
|
+
env['rack.input'].read
|
7
|
+
nr = 1
|
8
|
+
env["PATH_INFO"] =~ %r{/([\d\.]+)\z} and nr = $1.to_f
|
9
|
+
|
10
|
+
Rainbows.sleep(nr)
|
11
|
+
|
12
|
+
[ 200, {'Content-Type' => 'text/plain'}, [ "Hello\n" ] ]
|
13
|
+
}
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# don't set nr_client for Rev, only _one_ app running at once :x
|
3
|
+
nr_client=${nr_client-2}
|
4
|
+
. ./test-lib.sh
|
5
|
+
|
6
|
+
t_plan 19 "reopen rotated logs"
|
7
|
+
|
8
|
+
t_begin "setup and startup" && {
|
9
|
+
rtmpfiles curl_out curl_err r_rot
|
10
|
+
zbatery_setup $model
|
11
|
+
zbatery -D sleep.ru -c $unicorn_config
|
12
|
+
zbatery_wait_start
|
13
|
+
}
|
14
|
+
|
15
|
+
t_begin "ensure server is responsive" && {
|
16
|
+
curl -sSf http://$listen/ >/dev/null
|
17
|
+
}
|
18
|
+
|
19
|
+
t_begin "start $nr_client concurrent requests" && {
|
20
|
+
start=$(date +%s)
|
21
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
22
|
+
do
|
23
|
+
( curl -sSf http://$listen/2 >> $curl_out 2>> $curl_err ) &
|
24
|
+
done
|
25
|
+
}
|
26
|
+
|
27
|
+
t_begin "ensure stderr log is clean" && check_stderr
|
28
|
+
|
29
|
+
t_begin "external log rotation" && {
|
30
|
+
rm -f $r_rot
|
31
|
+
mv $r_err $r_rot
|
32
|
+
}
|
33
|
+
|
34
|
+
t_begin "send reopen log signal (USR1)" && {
|
35
|
+
kill -USR1 $zbatery_pid
|
36
|
+
}
|
37
|
+
|
38
|
+
t_begin "wait for rotated log to reappear" && {
|
39
|
+
nr=60
|
40
|
+
while ! test -f $r_err && test $nr -ge 0
|
41
|
+
do
|
42
|
+
sleep 1
|
43
|
+
nr=$(( $nr - 1 ))
|
44
|
+
done
|
45
|
+
}
|
46
|
+
|
47
|
+
t_begin "wait for worker to reopen logs" && {
|
48
|
+
nr=60
|
49
|
+
re="worker=.* done reopening logs"
|
50
|
+
while ! grep "$re" < $r_err >/dev/null && test $nr -ge 0
|
51
|
+
do
|
52
|
+
sleep 1
|
53
|
+
nr=$(( $nr - 1 ))
|
54
|
+
done
|
55
|
+
}
|
56
|
+
|
57
|
+
dbgcat r_rot
|
58
|
+
dbgcat r_err
|
59
|
+
|
60
|
+
t_begin "wait curl requests to finish" && {
|
61
|
+
wait
|
62
|
+
t_info elapsed=$(( $(date +%s) - $start ))
|
63
|
+
}
|
64
|
+
|
65
|
+
t_begin "ensure no errors from curl" && {
|
66
|
+
test ! -s $curl_err
|
67
|
+
}
|
68
|
+
|
69
|
+
t_begin "curl got $nr_client responses" && {
|
70
|
+
test "$(wc -l < $curl_out)" -eq $nr_client
|
71
|
+
}
|
72
|
+
|
73
|
+
t_begin "all responses were identical" && {
|
74
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
75
|
+
test "$nr" -eq 1
|
76
|
+
}
|
77
|
+
|
78
|
+
t_begin 'response was "Hello"' && {
|
79
|
+
test x$(sort < $curl_out | uniq) = xHello
|
80
|
+
}
|
81
|
+
|
82
|
+
t_begin "current server stderr is clean" && check_stderr
|
83
|
+
|
84
|
+
t_begin "rotated stderr is clean" && {
|
85
|
+
check_stderr $r_rot
|
86
|
+
}
|
87
|
+
|
88
|
+
t_begin "server is now writing logs to new stderr" && {
|
89
|
+
before_rot=$(wc -c < $r_rot)
|
90
|
+
before_err=$(wc -c < $r_err)
|
91
|
+
curl -sSfv http://$listen/
|
92
|
+
after_rot=$(wc -c < $r_rot)
|
93
|
+
after_err=$(wc -c < $r_err)
|
94
|
+
test $after_rot -eq $before_rot
|
95
|
+
test $after_err -gt $before_err
|
96
|
+
}
|
97
|
+
|
98
|
+
t_begin "stop server" && {
|
99
|
+
kill $zbatery_pid
|
100
|
+
}
|
101
|
+
|
102
|
+
dbgcat r_err
|
103
|
+
|
104
|
+
t_begin "current server stderr is clean" && check_stderr
|
105
|
+
t_begin "rotated stderr is clean" && check_stderr $r_rot
|
106
|
+
|
107
|
+
t_done
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
test -r random_blob || die "random_blob required, run with 'make $0'"
|
4
|
+
|
5
|
+
if ! grep -v ^VmRSS: /proc/self/status >/dev/null 2>&1
|
6
|
+
then
|
7
|
+
t_info "skipping, can't read RSS from /proc/self/status"
|
8
|
+
exit 0
|
9
|
+
fi
|
10
|
+
|
11
|
+
t_plan 10 "large file response slurp avoidance for $model"
|
12
|
+
|
13
|
+
t_begin "setup and startup" && {
|
14
|
+
rtmpfiles curl_out
|
15
|
+
zbatery_setup $model
|
16
|
+
# can't load Rack::Lint here since it'll cause Rev to slurp
|
17
|
+
zbatery -E none -D large-file-response.ru -c $unicorn_config
|
18
|
+
zbatery_wait_start
|
19
|
+
}
|
20
|
+
|
21
|
+
t_begin "read random blob size" && {
|
22
|
+
random_blob_size=$(wc -c < random_blob)
|
23
|
+
}
|
24
|
+
|
25
|
+
t_begin "read current RSS" && {
|
26
|
+
curl -v http://$listen/rss
|
27
|
+
dbgcat r_err
|
28
|
+
rss_before=$(curl -sSfv http://$listen/rss)
|
29
|
+
t_info "rss_before=$rss_before"
|
30
|
+
}
|
31
|
+
|
32
|
+
t_begin "send a series HTTP/1.1 requests sequentially" && {
|
33
|
+
for i in a b c
|
34
|
+
do
|
35
|
+
size=$( (curl -sSfv http://$listen/random_blob &&
|
36
|
+
echo ok >$ok) |wc -c)
|
37
|
+
test $size -eq $random_blob_size
|
38
|
+
test xok = x$(cat $ok)
|
39
|
+
done
|
40
|
+
}
|
41
|
+
|
42
|
+
# this was a problem during development
|
43
|
+
t_begin "HTTP/1.0 test" && {
|
44
|
+
size=$( (curl -0 -sSfv http://$listen/random_blob &&
|
45
|
+
echo ok >$ok) |wc -c)
|
46
|
+
test $size -eq $random_blob_size
|
47
|
+
test xok = x$(cat $ok)
|
48
|
+
}
|
49
|
+
|
50
|
+
t_begin "HTTP/0.9 test" && {
|
51
|
+
(
|
52
|
+
printf 'GET /random_blob\r\n'
|
53
|
+
cat $fifo > $tmp &
|
54
|
+
wait
|
55
|
+
echo ok > $ok
|
56
|
+
) | socat - TCP:$listen > $fifo
|
57
|
+
cmp $tmp random_blob
|
58
|
+
test xok = x$(cat $ok)
|
59
|
+
}
|
60
|
+
|
61
|
+
dbgcat r_err
|
62
|
+
|
63
|
+
t_begin "read RSS again" && {
|
64
|
+
curl -v http://$listen/rss
|
65
|
+
rss_after=$(curl -sSfv http://$listen/rss)
|
66
|
+
t_info "rss_after=$rss_after"
|
67
|
+
}
|
68
|
+
|
69
|
+
t_begin "shutdown server" && {
|
70
|
+
kill -QUIT $zbatery_pid
|
71
|
+
}
|
72
|
+
|
73
|
+
t_begin "compare RSS before and after" && {
|
74
|
+
diff=$(( $rss_after - $rss_before ))
|
75
|
+
t_info "test diff=$diff < orig=$random_blob_size"
|
76
|
+
test $diff -le $random_blob_size
|
77
|
+
}
|
78
|
+
|
79
|
+
dbgcat r_err
|
80
|
+
|
81
|
+
t_begin "check stderr" && check_stderr
|
82
|
+
|
83
|
+
t_done
|
data/t/test-lib.sh
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
# Copyright (c) 2009 Rainbows! developers
|
3
|
+
. ./my-tap-lib.sh
|
4
|
+
|
5
|
+
set +u
|
6
|
+
if test -z "$model"
|
7
|
+
then
|
8
|
+
# defaulting to Base would unfortunately fail some concurrency tests
|
9
|
+
model=ThreadSpawn
|
10
|
+
t_info "model undefined, defaulting to $model"
|
11
|
+
fi
|
12
|
+
|
13
|
+
set -e
|
14
|
+
RUBY="${RUBY-ruby}"
|
15
|
+
RUBY_VERSION=${RUBY_VERSION-$($RUBY -e 'puts RUBY_VERSION')}
|
16
|
+
t_pfx=$PWD/trash/$model.$T-$RUBY_VERSION
|
17
|
+
set -u
|
18
|
+
|
19
|
+
PATH=$PWD/bin:$PATH
|
20
|
+
export PATH
|
21
|
+
|
22
|
+
test -x $PWD/bin/unused_listen || die "must be run in 't' directory"
|
23
|
+
|
24
|
+
wait_for_pid () {
|
25
|
+
path="$1"
|
26
|
+
nr=30
|
27
|
+
while ! test -s "$path" && test $nr -gt 0
|
28
|
+
do
|
29
|
+
nr=$(($nr - 1))
|
30
|
+
sleep 1
|
31
|
+
done
|
32
|
+
}
|
33
|
+
|
34
|
+
# requires $1 and prints out the value of $2
|
35
|
+
require_check () {
|
36
|
+
lib=$1
|
37
|
+
const=$2
|
38
|
+
if ! $RUBY -r$lib -e "puts $const" >/dev/null 2>&1
|
39
|
+
then
|
40
|
+
t_info "skipping $T since we don't have $lib"
|
41
|
+
exit 0
|
42
|
+
fi
|
43
|
+
}
|
44
|
+
|
45
|
+
# given a list of variable names, create temporary files and assign
|
46
|
+
# the pathnames to those variables
|
47
|
+
rtmpfiles () {
|
48
|
+
for id in "$@"
|
49
|
+
do
|
50
|
+
name=$id
|
51
|
+
_tmp=$t_pfx.$id
|
52
|
+
eval "$id=$_tmp"
|
53
|
+
|
54
|
+
case $name in
|
55
|
+
*fifo)
|
56
|
+
rm -f $_tmp
|
57
|
+
mkfifo $_tmp
|
58
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
59
|
+
;;
|
60
|
+
*socket)
|
61
|
+
rm -f $_tmp
|
62
|
+
T_RM_LIST="$T_RM_LIST $_tmp"
|
63
|
+
;;
|
64
|
+
*)
|
65
|
+
> $_tmp
|
66
|
+
T_OK_RM_LIST="$T_OK_RM_LIST $_tmp"
|
67
|
+
;;
|
68
|
+
esac
|
69
|
+
done
|
70
|
+
}
|
71
|
+
|
72
|
+
dbgcat () {
|
73
|
+
id=$1
|
74
|
+
eval '_file=$'$id
|
75
|
+
echo "==> $id <=="
|
76
|
+
sed -e "s/^/$id:/" < $_file
|
77
|
+
}
|
78
|
+
|
79
|
+
check_stderr () {
|
80
|
+
set +u
|
81
|
+
_r_err=${1-${r_err}}
|
82
|
+
set -u
|
83
|
+
if grep -i Error $_r_err
|
84
|
+
then
|
85
|
+
die "Errors found in $_r_err"
|
86
|
+
elif grep SIGKILL $_r_err
|
87
|
+
then
|
88
|
+
die "SIGKILL found in $_r_err"
|
89
|
+
fi
|
90
|
+
}
|
91
|
+
|
92
|
+
# zbatery_setup [ MODEL [ WORKER_CONNECTIONS ] ]
|
93
|
+
zbatery_setup () {
|
94
|
+
eval $(unused_listen)
|
95
|
+
rtmpfiles unicorn_config pid r_err r_out fifo tmp ok
|
96
|
+
cat > $unicorn_config <<EOF
|
97
|
+
listen "$listen"
|
98
|
+
pid "$pid"
|
99
|
+
stderr_path "$r_err"
|
100
|
+
stdout_path "$r_out"
|
101
|
+
|
102
|
+
after_fork do |server, worker|
|
103
|
+
# test script will block while reading from $fifo,
|
104
|
+
# so notify the script on the first worker we spawn
|
105
|
+
# by opening the FIFO
|
106
|
+
if worker.nr == 0
|
107
|
+
File.open("$fifo", "wb") { |fp| fp.syswrite "START" }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
EOF
|
111
|
+
{
|
112
|
+
# set a higher default for tests since we run heavily-loaded
|
113
|
+
# boxes and sometimes sleep 1s in tests
|
114
|
+
kato=5
|
115
|
+
echo 'Rainbows! do'
|
116
|
+
if test $# -ge 1
|
117
|
+
then
|
118
|
+
echo " use :$1"
|
119
|
+
test $# -eq 2 && echo " worker_connections $2"
|
120
|
+
if test $# -eq 3
|
121
|
+
then
|
122
|
+
echo " keepalive_timeout $3"
|
123
|
+
else
|
124
|
+
echo " keepalive_timeout $kato"
|
125
|
+
fi
|
126
|
+
else
|
127
|
+
echo " use :$model"
|
128
|
+
echo " keepalive_timeout $kato"
|
129
|
+
fi
|
130
|
+
echo end
|
131
|
+
} >> $unicorn_config
|
132
|
+
}
|
133
|
+
|
134
|
+
zbatery_wait_start () {
|
135
|
+
# "cat $fifo" will block until the before_fork hook is called in
|
136
|
+
# the Unicorn config file
|
137
|
+
test xSTART = x"$(cat $fifo)"
|
138
|
+
zbatery_pid=$(cat $pid)
|
139
|
+
}
|
140
|
+
|
141
|
+
rsha1 () {
|
142
|
+
_cmd="$(which sha1sum 2>/dev/null || :)"
|
143
|
+
test -n "$_cmd" || _cmd="$(which openssl 2>/dev/null || :) sha1"
|
144
|
+
test "$_cmd" != " sha1" || _cmd="$(which gsha1sum 2>/dev/null || :)"
|
145
|
+
|
146
|
+
# last resort, see comments in sha1sum.rb for reasoning
|
147
|
+
test -n "$_cmd" || _cmd=sha1sum.rb
|
148
|
+
expr "$($_cmd < random_blob)" : '\([a-f0-9]\{40\}\)'
|
149
|
+
}
|
150
|
+
|
151
|
+
case $model in
|
152
|
+
Rev) require_check rev Rev::VERSION ;;
|
153
|
+
Revactor) require_check revactor Revactor::VERSION ;;
|
154
|
+
EventMachine) require_check eventmachine EventMachine::VERSION ;;
|
155
|
+
esac
|
data/zbatery.gemspec
CHANGED
@@ -53,7 +53,8 @@ Gem::Specification.new do |s|
|
|
53
53
|
# rainbows 0.90.2 depends on unicorn 0.96.1,
|
54
54
|
# unicorn 0.96.0 and before had a memory leak
|
55
55
|
# that was only triggered in Rainbows!/Zbatery
|
56
|
-
s.add_dependency(%q<
|
56
|
+
s.add_dependency(%q<unicorn>, ["~> 0.97.0"])
|
57
|
+
s.add_dependency(%q<rainbows>, [">= 0.91.0", "<= 1.0.0"])
|
57
58
|
|
58
59
|
# s.licenses = %w(GPLv2 Ruby) # accessor not compatible with older RubyGems
|
59
60
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zbatery
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zbatery hackers
|
@@ -9,9 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-03-01 00:00:00 +00:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: unicorn
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.97.0
|
24
|
+
version:
|
15
25
|
- !ruby/object:Gem::Dependency
|
16
26
|
name: rainbows
|
17
27
|
type: :runtime
|
@@ -20,7 +30,7 @@ dependencies:
|
|
20
30
|
requirements:
|
21
31
|
- - ">="
|
22
32
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.
|
33
|
+
version: 0.91.0
|
24
34
|
- - <=
|
25
35
|
- !ruby/object:Gem::Version
|
26
36
|
version: 1.0.0
|
@@ -67,6 +77,18 @@ files:
|
|
67
77
|
- local.mk.sample
|
68
78
|
- man/man1/zbatery.1
|
69
79
|
- setup.rb
|
80
|
+
- t/.gitignore
|
81
|
+
- t/GNUmakefile
|
82
|
+
- t/bin/content-md5-put
|
83
|
+
- t/bin/sha1sum.rb
|
84
|
+
- t/bin/unused_listen
|
85
|
+
- t/bin/utee
|
86
|
+
- t/large-file-response.ru
|
87
|
+
- t/my-tap-lib.sh
|
88
|
+
- t/sleep.ru
|
89
|
+
- t/t0003-reopen-logs.sh
|
90
|
+
- t/t0005-large-file-response.sh
|
91
|
+
- t/test-lib.sh
|
70
92
|
- zbatery.gemspec
|
71
93
|
has_rdoc: true
|
72
94
|
homepage: http://zbatery.bogomip.org/
|