rainbows 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/.document +6 -5
- data/DEPLOY +13 -0
- data/GIT-VERSION-GEN +1 -1
- data/GNUmakefile +1 -1
- data/README +32 -6
- data/SIGNALS +11 -7
- data/TODO +2 -3
- data/lib/rainbows.rb +10 -3
- data/lib/rainbows/app_pool.rb +90 -0
- data/lib/rainbows/base.rb +41 -4
- data/lib/rainbows/const.rb +1 -6
- data/lib/rainbows/http_server.rb +1 -1
- data/lib/rainbows/rev.rb +174 -0
- data/lib/rainbows/revactor.rb +40 -37
- data/lib/rainbows/thread_pool.rb +31 -57
- data/lib/rainbows/thread_spawn.rb +32 -45
- data/local.mk.sample +4 -3
- data/t/.gitignore +1 -2
- data/t/GNUmakefile +21 -7
- data/t/README +42 -0
- data/t/bin/content-md5-put +36 -0
- data/t/bin/unused_listen +1 -1
- data/t/content-md5.ru +23 -0
- data/t/sleep.ru +11 -0
- data/t/t0000-basic.sh +29 -3
- data/t/t1000-thread-pool-basic.sh +5 -6
- data/t/t1000.ru +5 -1
- data/t/t1002-thread-pool-graceful.sh +37 -0
- data/t/t2000-thread-spawn-basic.sh +4 -6
- data/t/t2000.ru +5 -1
- data/t/t2002-thread-spawn-graceful.sh +37 -0
- data/t/t3000-revactor-basic.sh +4 -6
- data/t/t3000.ru +5 -1
- data/t/t3001-revactor-pipeline.sh +46 -0
- data/t/t3002-revactor-graceful.sh +38 -0
- data/t/t3003-revactor-reopen-logs.sh +54 -0
- data/t/t3100-revactor-tee-input.sh +8 -13
- data/t/t4000-rev-basic.sh +51 -0
- data/t/t4000.ru +9 -0
- data/t/t4002-rev-graceful.sh +52 -0
- data/t/t4003-rev-parser-error.sh +34 -0
- data/t/t4100-rev-rack-input.sh +44 -0
- data/t/t4101-rev-rack-input-trailer.sh +51 -0
- data/t/t9000-rack-app-pool.sh +37 -0
- data/t/t9000.ru +14 -0
- data/t/test-lib.sh +29 -2
- data/vs_Unicorn +50 -1
- metadata +28 -6
data/t/README
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
= Rainbows! test suite
|
2
|
+
|
3
|
+
These are all integration tests that start the server on random, unused
|
4
|
+
TCP ports or Unix domain sockets. They're all designed to run
|
5
|
+
concurrently with other tests to minimize test time, but tests may be
|
6
|
+
run independently as well.
|
7
|
+
|
8
|
+
We write our tests in Bourne shell because that's what we're
|
9
|
+
comfortable writing integration tests with.
|
10
|
+
|
11
|
+
== Requirements
|
12
|
+
|
13
|
+
* {Ruby 1.8 or 1.9}[http://www.ruby-lang.org/] (duh!)
|
14
|
+
* {GNU make}[http://www.gnu.org/software/make/]
|
15
|
+
* {socat}[http://www.dest-unreach.org/socat/]
|
16
|
+
* {curl}[http://curl.haxx.se/]
|
17
|
+
* standard UNIX shell utilities (Bourne sh, awk, sed, grep, tee, ...)
|
18
|
+
|
19
|
+
We do not use bashisms or any non-portable, non-POSIX constructs
|
20
|
+
in our shell code. We use the "pipefail" option if available and
|
21
|
+
mainly test with {ksh}[http://kornshell.com/], but occasionally
|
22
|
+
with {dash}[http://gondor.apana.org.au/~herbert/dash/] and
|
23
|
+
{bash}[http://www.gnu.org/software/bash/], too.
|
24
|
+
|
25
|
+
== Running Tests
|
26
|
+
|
27
|
+
To run the entire test suite with 8 tests running at once:
|
28
|
+
|
29
|
+
make -j8
|
30
|
+
|
31
|
+
To run one individual test:
|
32
|
+
|
33
|
+
make t0000-basic.sh
|
34
|
+
|
35
|
+
You may also increase verbosity by setting the "V" variable for
|
36
|
+
GNU make. To disable trapping of stdout/stderr:
|
37
|
+
|
38
|
+
make V=1
|
39
|
+
|
40
|
+
To enable the "set -x" option in shell scripts to trace execution
|
41
|
+
|
42
|
+
make V=2
|
@@ -0,0 +1,36 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# simple chunked HTTP PUT request generator (and just that),
|
3
|
+
# it reads stdin and writes to stdout so socat can write to a
|
4
|
+
# UNIX or TCP socket (or to another filter or file) along with
|
5
|
+
# a Content-MD5 trailer.
|
6
|
+
# -*- encoding: binary -*-
|
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.read(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/unused_listen
CHANGED
data/t/content-md5.ru
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# SHA1 checksum generator
|
2
|
+
bs = ENV['bs'] ? ENV['bs'].to_i : 4096
|
3
|
+
require 'digest/md5'
|
4
|
+
use Rack::ContentLength
|
5
|
+
app = lambda do |env|
|
6
|
+
/\A100-continue\z/i =~ env['HTTP_EXPECT'] and
|
7
|
+
return [ 100, {}, [] ]
|
8
|
+
digest = Digest::MD5.new
|
9
|
+
input = env['rack.input']
|
10
|
+
if buf = input.read(bs)
|
11
|
+
begin
|
12
|
+
digest.update(buf)
|
13
|
+
end while input.read(bs, buf)
|
14
|
+
end
|
15
|
+
|
16
|
+
expect = env['HTTP_CONTENT_MD5']
|
17
|
+
readed = [ digest.digest ].pack('m').strip
|
18
|
+
body = "expect=#{expect}\nreaded=#{readed}\n"
|
19
|
+
status = expect == readed ? 200 : 500
|
20
|
+
|
21
|
+
[ status, {'Content-Type' => 'text/plain'}, [ body ] ]
|
22
|
+
end
|
23
|
+
run app
|
data/t/sleep.ru
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
use Rack::ContentLength
|
2
|
+
use Rack::ContentType
|
3
|
+
sleep_class = ENV['SLEEP_CLASS']
|
4
|
+
sleep_class = sleep_class ? Object.const_get(sleep_class) : Kernel
|
5
|
+
$stderr.puts "sleep_class=#{sleep_class.inspect}"
|
6
|
+
run lambda { |env|
|
7
|
+
nr = 1
|
8
|
+
env["PATH_INFO"] =~ %r{/([\d\.]+)\z} and nr = $1.to_f
|
9
|
+
sleep_class.sleep(nr)
|
10
|
+
[ 200, {}, [ "Hello\n" ] ]
|
11
|
+
}
|
data/t/t0000-basic.sh
CHANGED
@@ -2,10 +2,36 @@
|
|
2
2
|
. ./test-lib.sh
|
3
3
|
|
4
4
|
eval $(unused_listen)
|
5
|
-
pid
|
6
|
-
TEST_RM_LIST="$TEST_RM_LIST $lock_path $pid"
|
5
|
+
rtmpfiles pid tmp ok fifo
|
7
6
|
|
8
|
-
|
7
|
+
rm -f $fifo
|
8
|
+
mkfifo $fifo
|
9
|
+
|
10
|
+
rainbows -D t0000.ru -l $listen --pid $pid &
|
9
11
|
wait_for_pid $pid
|
12
|
+
|
13
|
+
echo "single request"
|
10
14
|
curl -sSfv http://$listen/
|
15
|
+
|
16
|
+
echo "two requests with keepalive"
|
17
|
+
curl -sSfv http://$listen/a http://$listen/b > $tmp 2>&1
|
18
|
+
grep 'Re-using existing connection' < $tmp
|
19
|
+
|
20
|
+
echo "pipelining partial requests"
|
21
|
+
req='GET / HTTP/1.1\r\nHost: foo\r\n'
|
22
|
+
(
|
23
|
+
printf "$req"'\r\n'"$req"
|
24
|
+
cat $fifo > $tmp &
|
25
|
+
sleep 1
|
26
|
+
printf 'Connection: close\r\n\r\n'
|
27
|
+
echo ok > $ok
|
28
|
+
) | socat - TCP:$listen > $fifo
|
29
|
+
|
11
30
|
kill $(cat $pid)
|
31
|
+
|
32
|
+
# sed -ne 's/^/------/p' < $tmp
|
33
|
+
test 2 -eq $(grep '^HTTP/1.1' $tmp | wc -l)
|
34
|
+
test 2 -eq $(grep '^HTTP/1.1 200 OK' $tmp | wc -l)
|
35
|
+
test 1 -eq $(grep '^Connection: keep-alive' $tmp | wc -l)
|
36
|
+
test 1 -eq $(grep '^Connection: close' $tmp | wc -l)
|
37
|
+
test x"$(cat $ok)" = xok
|
@@ -2,17 +2,14 @@
|
|
2
2
|
. ./test-lib.sh
|
3
3
|
|
4
4
|
eval $(unused_listen)
|
5
|
-
unicorn_config
|
6
|
-
curl_out=$(mktemp -t rainbows.$$.curl.out.XXXXXXXX)
|
7
|
-
curl_err=$(mktemp -t rainbows.$$.curl.err.XXXXXXXX)
|
8
|
-
pid=$(mktemp -t rainbows.$$.pid.XXXXXXXX)
|
9
|
-
TEST_RM_LIST="$TEST_RM_LIST $unicorn_config $lock_path $pid"
|
10
|
-
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
5
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
11
6
|
|
12
7
|
nr_client=30
|
13
8
|
nr_thread=10
|
14
9
|
|
15
10
|
cat > $unicorn_config <<EOF
|
11
|
+
stderr_path "$r_err"
|
12
|
+
stdout_path "$r_out"
|
16
13
|
listen "$listen"
|
17
14
|
pid "$pid"
|
18
15
|
Rainbows! do
|
@@ -41,3 +38,5 @@ nr=$(sort < $curl_out | uniq | wc -l)
|
|
41
38
|
|
42
39
|
test "$nr" -le $nr_thread
|
43
40
|
test "$nr" -gt 1
|
41
|
+
|
42
|
+
! grep Error $r_err
|
data/t/t1000.ru
CHANGED
@@ -2,5 +2,9 @@ use Rack::ContentLength
|
|
2
2
|
use Rack::ContentType
|
3
3
|
run lambda { |env|
|
4
4
|
sleep 1
|
5
|
-
[
|
5
|
+
if env['rack.multithread'] && env['rainbows.model'] == :ThreadPool
|
6
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
7
|
+
else
|
8
|
+
raise "rack.multithread is not true"
|
9
|
+
end
|
6
10
|
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
eval $(unused_listen)
|
5
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
6
|
+
nr_thread=10
|
7
|
+
nr_client=10
|
8
|
+
cat > $unicorn_config <<EOF
|
9
|
+
listen "$listen"
|
10
|
+
stderr_path "$r_err"
|
11
|
+
stdout_path "$r_out"
|
12
|
+
pid "$pid"
|
13
|
+
Rainbows! do
|
14
|
+
use :ThreadPool
|
15
|
+
worker_connections $nr_thread
|
16
|
+
end
|
17
|
+
EOF
|
18
|
+
|
19
|
+
rainbows -D sleep.ru -c $unicorn_config
|
20
|
+
wait_for_pid $pid
|
21
|
+
|
22
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
23
|
+
do
|
24
|
+
curl -sSf http://$listen/5 >> $curl_out 2>> $curl_err &
|
25
|
+
done
|
26
|
+
sleep 2
|
27
|
+
kill -QUIT $(cat $pid)
|
28
|
+
wait
|
29
|
+
|
30
|
+
dbgcat r_err
|
31
|
+
! test -s $curl_err
|
32
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
33
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
34
|
+
|
35
|
+
test "$nr" -eq 1
|
36
|
+
test x$(sort < $curl_out | uniq) = xHello
|
37
|
+
! grep Error $r_err
|
@@ -2,17 +2,14 @@
|
|
2
2
|
. ./test-lib.sh
|
3
3
|
|
4
4
|
eval $(unused_listen)
|
5
|
-
unicorn_config
|
6
|
-
curl_out=$(mktemp -t rainbows.$$.curl.out.XXXXXXXX)
|
7
|
-
curl_err=$(mktemp -t rainbows.$$.curl.err.XXXXXXXX)
|
8
|
-
pid=$(mktemp -t rainbows.$$.pid.XXXXXXXX)
|
9
|
-
TEST_RM_LIST="$TEST_RM_LIST $unicorn_config $lock_path $pid"
|
10
|
-
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
5
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
11
6
|
|
12
7
|
nr_client=30
|
13
8
|
nr_thread=10
|
14
9
|
|
15
10
|
cat > $unicorn_config <<EOF
|
11
|
+
stderr_path "$r_err"
|
12
|
+
stdout_path "$r_out"
|
16
13
|
listen "$listen"
|
17
14
|
pid "$pid"
|
18
15
|
Rainbows! do
|
@@ -38,3 +35,4 @@ kill $(cat $pid)
|
|
38
35
|
test x"$(wc -l < $curl_out)" = x$nr_client
|
39
36
|
nr=$(sort < $curl_out | uniq | wc -l)
|
40
37
|
test "$nr" -eq $nr_client
|
38
|
+
! grep Error $r_err
|
data/t/t2000.ru
CHANGED
@@ -2,5 +2,9 @@ use Rack::ContentLength
|
|
2
2
|
use Rack::ContentType
|
3
3
|
run lambda { |env|
|
4
4
|
sleep 1
|
5
|
-
[
|
5
|
+
if env['rack.multithread'] && env['rainbows.model'] == :ThreadSpawn
|
6
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
7
|
+
else
|
8
|
+
raise "rack.multithread is not true"
|
9
|
+
end
|
6
10
|
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
|
4
|
+
eval $(unused_listen)
|
5
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
6
|
+
nr_thread=10
|
7
|
+
nr_client=10
|
8
|
+
cat > $unicorn_config <<EOF
|
9
|
+
listen "$listen"
|
10
|
+
stderr_path "$r_err"
|
11
|
+
stdout_path "$r_out"
|
12
|
+
pid "$pid"
|
13
|
+
Rainbows! do
|
14
|
+
use :ThreadSpawn
|
15
|
+
worker_connections $nr_thread
|
16
|
+
end
|
17
|
+
EOF
|
18
|
+
|
19
|
+
rainbows -D sleep.ru -c $unicorn_config
|
20
|
+
wait_for_pid $pid
|
21
|
+
|
22
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
23
|
+
do
|
24
|
+
curl -sSf http://$listen/5 >> $curl_out 2>> $curl_err &
|
25
|
+
done
|
26
|
+
sleep 2
|
27
|
+
kill -QUIT $(cat $pid)
|
28
|
+
wait
|
29
|
+
|
30
|
+
dbgcat r_err
|
31
|
+
! test -s $curl_err
|
32
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
33
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
34
|
+
|
35
|
+
test "$nr" -eq 1
|
36
|
+
test x$(sort < $curl_out | uniq) = xHello
|
37
|
+
! grep Error $r_err
|
data/t/t3000-revactor-basic.sh
CHANGED
@@ -3,12 +3,7 @@
|
|
3
3
|
require_revactor
|
4
4
|
|
5
5
|
eval $(unused_listen)
|
6
|
-
unicorn_config
|
7
|
-
curl_out=$(mktemp -t rainbows.$$.curl.out.XXXXXXXX)
|
8
|
-
curl_err=$(mktemp -t rainbows.$$.curl.err.XXXXXXXX)
|
9
|
-
pid=$(mktemp -t rainbows.$$.pid.XXXXXXXX)
|
10
|
-
TEST_RM_LIST="$TEST_RM_LIST $pid $unicorn_config $lock_path"
|
11
|
-
TEST_RM_LIST="$TEST_RM_LIST $curl_out $curl_err"
|
6
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
12
7
|
|
13
8
|
nr_client=30
|
14
9
|
nr_actor=10
|
@@ -16,6 +11,8 @@ nr_actor=10
|
|
16
11
|
cat > $unicorn_config <<EOF
|
17
12
|
listen "$listen"
|
18
13
|
pid "$pid"
|
14
|
+
stderr_path "$r_err"
|
15
|
+
stdout_path "$r_out"
|
19
16
|
Rainbows! do
|
20
17
|
use :Revactor
|
21
18
|
worker_connections $nr_actor
|
@@ -40,3 +37,4 @@ test x"$(wc -l < $curl_out)" = x$nr_client
|
|
40
37
|
nr=$(sort < $curl_out | uniq | wc -l)
|
41
38
|
|
42
39
|
test "$nr" -eq 1
|
40
|
+
! grep Error $r_err
|
data/t/t3000.ru
CHANGED
@@ -2,5 +2,9 @@ use Rack::ContentLength
|
|
2
2
|
use Rack::ContentType
|
3
3
|
run lambda { |env|
|
4
4
|
Actor.sleep 1
|
5
|
-
[
|
5
|
+
if env['rack.multithread'] == false && env['rainbows.model'] == :Revactor
|
6
|
+
[ 200, {}, [ Thread.current.inspect << "\n" ] ]
|
7
|
+
else
|
8
|
+
raise "rack.multithread is true"
|
9
|
+
end
|
6
10
|
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
require_revactor
|
4
|
+
|
5
|
+
eval $(unused_listen)
|
6
|
+
rtmpfiles unicorn_config curl_out curl_err pid fifo tmp ok r_err r_out
|
7
|
+
|
8
|
+
rm -f $fifo
|
9
|
+
mkfifo $fifo
|
10
|
+
|
11
|
+
cat > $unicorn_config <<EOF
|
12
|
+
stderr_path "$r_err"
|
13
|
+
stdout_path "$r_out"
|
14
|
+
listen "$listen"
|
15
|
+
pid "$pid"
|
16
|
+
Rainbows! do
|
17
|
+
use :Revactor
|
18
|
+
end
|
19
|
+
EOF
|
20
|
+
|
21
|
+
rainbows -D t0000.ru -c $unicorn_config
|
22
|
+
wait_for_pid $pid
|
23
|
+
|
24
|
+
echo "two requests with keepalive"
|
25
|
+
curl -sSfv http://$listen/a http://$listen/b > $tmp 2>&1
|
26
|
+
grep 'Re-using existing connection' < $tmp
|
27
|
+
|
28
|
+
echo "pipelining partial requests"
|
29
|
+
req='GET / HTTP/1.1\r\nHost: foo\r\n'
|
30
|
+
(
|
31
|
+
printf "$req"'\r\n'"$req"
|
32
|
+
cat $fifo > $tmp &
|
33
|
+
sleep 1
|
34
|
+
printf 'Connection: close\r\n\r\n'
|
35
|
+
echo ok > $ok
|
36
|
+
) | socat - TCP:$listen > $fifo
|
37
|
+
|
38
|
+
kill $(cat $pid)
|
39
|
+
|
40
|
+
dbgcat tmp
|
41
|
+
test 2 -eq $(grep '^HTTP/1.1' $tmp | wc -l)
|
42
|
+
test 2 -eq $(grep '^HTTP/1.1 200 OK' $tmp | wc -l)
|
43
|
+
test 1 -eq $(grep '^Connection: keep-alive' $tmp | wc -l)
|
44
|
+
test 1 -eq $(grep '^Connection: close' $tmp | wc -l)
|
45
|
+
test x"$(cat $ok)" = xok
|
46
|
+
! grep Error $r_err
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
. ./test-lib.sh
|
3
|
+
require_revactor
|
4
|
+
|
5
|
+
eval $(unused_listen)
|
6
|
+
rtmpfiles unicorn_config curl_out curl_err pid r_err r_out
|
7
|
+
nr_actor=10
|
8
|
+
nr_client=10
|
9
|
+
cat > $unicorn_config <<EOF
|
10
|
+
listen "$listen"
|
11
|
+
stderr_path "$r_err"
|
12
|
+
stdout_path "$r_out"
|
13
|
+
pid "$pid"
|
14
|
+
Rainbows! do
|
15
|
+
use :Revactor
|
16
|
+
worker_connections $nr_actor
|
17
|
+
end
|
18
|
+
EOF
|
19
|
+
|
20
|
+
SLEEP_CLASS=Actor rainbows -D sleep.ru -c $unicorn_config
|
21
|
+
wait_for_pid $pid
|
22
|
+
|
23
|
+
for i in $(awk "BEGIN{for(i=0;i<$nr_client;++i) print i}" </dev/null)
|
24
|
+
do
|
25
|
+
curl -sSf http://$listen/5 >> $curl_out 2>> $curl_err &
|
26
|
+
done
|
27
|
+
sleep 2
|
28
|
+
kill -QUIT $(cat $pid)
|
29
|
+
wait
|
30
|
+
|
31
|
+
dbgcat r_err
|
32
|
+
! test -s $curl_err
|
33
|
+
test x"$(wc -l < $curl_out)" = x$nr_client
|
34
|
+
nr=$(sort < $curl_out | uniq | wc -l)
|
35
|
+
|
36
|
+
test "$nr" -eq 1
|
37
|
+
test x$(sort < $curl_out | uniq) = xHello
|
38
|
+
! grep Error $r_err
|