http_spew 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
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
+ digest = Digest::MD5.new
7
+ input = env['rack.input']
8
+ if buf = input.read(bs)
9
+ begin
10
+ digest.update(buf)
11
+ end while input.read(bs, buf)
12
+ end
13
+
14
+ expect = env['HTTP_CONTENT_MD5']
15
+ readed = [ digest.digest ].pack('m').strip
16
+ body = "expect=#{expect}\nreaded=#{readed}\n"
17
+ status = expect == readed ? 200 : 500
18
+
19
+ [ status, {'Content-Type' => 'text/plain'}, [ body ] ]
20
+ end
21
+ run app
data/test/helper.rb ADDED
@@ -0,0 +1,41 @@
1
+ # -*- encoding: binary -*-
2
+ $stderr.sync = $stdout.sync = true
3
+ require "test/unit"
4
+ require "digest/sha1"
5
+ require "stringio"
6
+ require "tmpdir"
7
+ require "socket"
8
+ require "rack"
9
+ require "tempfile"
10
+ $-w = true
11
+ require "http_spew"
12
+
13
+ def start_server(config, worker_processes = 4)
14
+ addr = ENV["TEST_HOST"] || "127.0.0.1"
15
+ sock = TCPServer.new(addr, 0)
16
+ port = sock.addr[1]
17
+ fifo = Tempfile.new("fifo")
18
+ fifo_path = fifo.path
19
+ fifo.close!
20
+ system("mkfifo", fifo_path) or abort "mkfifo: #$?"
21
+ cfg = Tempfile.new("unicorn.config")
22
+ cfg.puts "worker_processes #{worker_processes}"
23
+ cfg.puts "preload_app true"
24
+ cfg.puts <<EOF
25
+ after_fork do |s,w|
26
+ w.nr == (s.worker_processes - 1) and File.open("#{fifo_path}", "w").close
27
+ end
28
+ EOF
29
+ cfg.flush
30
+ pid = fork do
31
+ ENV["UNICORN_FD"] = sock.fileno.to_s
32
+ exec "unicorn", "-l#{addr}:#{port}", "-c#{cfg.path}", config
33
+ end
34
+ File.open(fifo_path).close
35
+ File.unlink fifo_path
36
+ [ addr, port, pid ]
37
+ end
38
+
39
+ def rand_data(nr)
40
+ File.open("/dev/urandom", "rb") { |fp| fp.read(nr) }
41
+ end
data/test/mirror.ru ADDED
@@ -0,0 +1,22 @@
1
+ # SHA1 checksum generator
2
+ bs = ENV['bs'] ? ENV['bs'].to_i : 16384
3
+ require 'digest/sha1'
4
+ require 'unicorn/preread_input'
5
+ use Unicorn::PrereadInput
6
+ class InputWrap < Struct.new(:input)
7
+ def each
8
+ buf = ""
9
+ while buf = input.read(0x4000, buf)
10
+ yield buf
11
+ end
12
+ end
13
+ end
14
+
15
+ app = lambda do |env|
16
+ headers = {
17
+ "Content-Type" => "application/octet-stream",
18
+ "Content-Length" => env["CONTENT_LENGTH"].to_s,
19
+ }
20
+ [ 200, headers, InputWrap.new(env["rack.input"]) ]
21
+ end
22
+ run app
data/test/sha1.ru ADDED
@@ -0,0 +1,18 @@
1
+ # SHA1 checksum generator
2
+ bs = ENV['bs'] ? ENV['bs'].to_i : 16384
3
+ require 'digest/sha1'
4
+ use Rack::ContentLength
5
+ app = lambda do |env|
6
+ /\A100-continue\z/i =~ env['HTTP_EXPECT'] and
7
+ return [ 100, {}, [] ]
8
+ digest = Digest::SHA1.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
+ [ 200, {'Content-Type' => 'text/plain'}, [ digest.hexdigest << "\n" ] ]
17
+ end
18
+ run app
@@ -0,0 +1,85 @@
1
+ # -*- encoding: binary -*-
2
+ require "./test/helper"
3
+
4
+ class TestContentMD5 < Test::Unit::TestCase
5
+ def setup
6
+ ENV["RACK_ENV"] = "deployment" # quiet Rack 1.2.1 bug
7
+ @addr, @port, @srv = start_server("./test/content-md5.ru", 1)
8
+ @sockaddr = Socket.pack_sockaddr_in(@port, @addr)
9
+ @env = {
10
+ "REQUEST_METHOD" => "PUT",
11
+ "REQUEST_URI" => "/",
12
+ "HTTP_HOST" => "example.com",
13
+ }
14
+ @tmpfiles = []
15
+ end
16
+
17
+ def teardown
18
+ Process.kill(:QUIT, @srv)
19
+ Process.waitpid2(@srv)
20
+ @tmpfiles.each { |tmp| tmp.closed? or tmp.close! }
21
+ end
22
+
23
+ def test_upload_with_md5
24
+ str = rand_data(123) * (8 * 1021 * 13)
25
+ expect = [Digest::MD5.digest(str)].pack("m").strip!
26
+ expect = "expect=#{expect}\nreaded=#{expect}\n"
27
+ @env["CONTENT_LENGTH"] = str.size.to_s
28
+ @env["rack.input"] = StringIO.new(str)
29
+ input = HTTP_Spew::ContentMD5.input(@env)
30
+ assert_nil @env["CONTENT_LENGTH"]
31
+ assert_equal "chunked", @env["HTTP_TRANSFER_ENCODING"]
32
+ req = HTTP_Spew::Request.new(@env, input, @sockaddr)
33
+ rv = HTTP_Spew.wait 1, [req], 666000
34
+ assert_equal 200, rv[0].response[0].to_i
35
+ body = ""
36
+ req.each { |buf| body << buf }
37
+ assert_equal body, expect
38
+ end
39
+
40
+ def test_upload_with_corrupt_md5
41
+ str = rand_data(123) * (8 * 1021 * 13)
42
+ expect = [Digest::MD5.digest(str)].pack("m").strip!
43
+ @env["HTTP_CONTENT_MD5"] = expect
44
+ str = rand_data(123) * (8 * 1021 * 13)
45
+ @env["CONTENT_LENGTH"] = str.size.to_s
46
+ @env["rack.input"] = StringIO.new(str)
47
+ input = HTTP_Spew::ContentMD5.input(@env)
48
+ assert_nil @env["CONTENT_LENGTH"]
49
+ assert_equal "chunked", @env["HTTP_TRANSFER_ENCODING"]
50
+ req = HTTP_Spew::Request.new(@env, input, @sockaddr)
51
+ rv = HTTP_Spew.wait 1, [req], 3600_000
52
+ assert_kind_of HTTP_Spew::ContentMD5::MismatchError, rv[0].error
53
+ end
54
+
55
+ def test_upload_with_corrupt_length
56
+ str = rand_data(123) * (8 * 1021 * 13)
57
+ expect = [Digest::MD5.digest(str)].pack("m").strip!
58
+ @env["HTTP_CONTENT_MD5"] = expect
59
+ str = rand_data(123) * (8 * 1021 * 13)
60
+ @env["CONTENT_LENGTH"] = (str.size + 1).to_s
61
+ @env["rack.input"] = StringIO.new(str)
62
+ input = HTTP_Spew::ContentMD5.input(@env)
63
+ assert_nil @env["CONTENT_LENGTH"]
64
+ assert_equal "chunked", @env["HTTP_TRANSFER_ENCODING"]
65
+ req = HTTP_Spew::Request.new(@env, input, @sockaddr)
66
+ rv = HTTP_Spew.wait 1, [req], 3600_000
67
+ assert_kind_of HTTP_Spew::ContentMD5::LengthError, rv[0].error
68
+ end
69
+
70
+ def test_upload_with_valid_md5
71
+ str = rand_data(123) * (8 * 1021 * 13)
72
+ expect = [Digest::MD5.digest(str)].pack("m").strip!
73
+ @env["HTTP_CONTENT_MD5"] = expect
74
+ @env["CONTENT_LENGTH"] = str.size.to_s
75
+ @env["rack.input"] = StringIO.new(str)
76
+ input = HTTP_Spew::ContentMD5.input(@env)
77
+ assert_nil @env["CONTENT_LENGTH"]
78
+ assert_equal "chunked", @env["HTTP_TRANSFER_ENCODING"]
79
+ req = HTTP_Spew::Request.new(@env, input, @sockaddr)
80
+ rv = HTTP_Spew.wait 1, [req], 3600_000
81
+ assert_equal 200, rv[0].response[0].to_i
82
+ body = ""
83
+ req.each { |buf| body << buf }
84
+ end
85
+ end
@@ -0,0 +1,47 @@
1
+ require "./test/helper"
2
+
3
+ class TestHitNRun < Test::Unit::TestCase
4
+ def setup
5
+ @addr, @port, @srv = start_server("./test/sha1.ru", 1)
6
+ @sockaddr = Socket.pack_sockaddr_in(@port, @addr)
7
+ @env = {
8
+ "REQUEST_METHOD" => "PUT",
9
+ "REQUEST_URI" => "/",
10
+ "HTTP_HOST" => "example.com",
11
+ }
12
+ @tmpfiles = []
13
+ end
14
+
15
+ def teardown
16
+ Process.kill(:QUIT, @srv)
17
+ Process.waitpid2(@srv)
18
+ @tmpfiles.each { |tmp| tmp.closed? or tmp.close! }
19
+ end
20
+
21
+ def test_request_with_existing_socket
22
+ sock = Kgio::Socket.new(@sockaddr)
23
+ req = HTTP_Spew::HitNRun.new(@env, nil, sock)
24
+ assert_equal sock, req.to_io
25
+ assert_nothing_raised { req.close }
26
+ assert sock.closed?
27
+ end
28
+
29
+ def test_request_single
30
+ req = HTTP_Spew::HitNRun.new(@env, nil, @sockaddr)
31
+ sym = req.resume
32
+ if sym == :wait_writable
33
+ set = Kgio.poll({req => sym}, 100)
34
+ assert_equal [ req ], set.keys
35
+ sym = req.resume
36
+ end
37
+ assert_equal HTTP_Spew::HitNRun::RESPONSE.object_id, sym.object_id
38
+ end
39
+
40
+ def test_request_loop
41
+ req = HTTP_Spew::HitNRun.new(@env, nil, @sockaddr)
42
+ until Array === (rv = req.resume)
43
+ Kgio.poll(req => rv)
44
+ end
45
+ assert_equal HTTP_Spew::HitNRun::RESPONSE.object_id, rv.object_id
46
+ end
47
+ end
@@ -0,0 +1,101 @@
1
+ # -*- encoding: binary -*-
2
+ require "./test/helper"
3
+
4
+ class TestInputSpray < Test::Unit::TestCase
5
+ BUF = (rand_data(128) * 1024 * 8 * 4).freeze
6
+ EXPECT = Digest::SHA1.hexdigest(BUF).freeze
7
+
8
+ def setup
9
+ io = StringIO.new(BUF)
10
+ @env = {
11
+ "rack.input" => io,
12
+ "CONTENT_LENGTH" => io.size.to_s
13
+ }
14
+ end
15
+
16
+ def test_spray_ok
17
+ sprayer = HTTP_Spew::InputSpray.new(@env, 2)
18
+ readers = sprayer.readers
19
+ assert_equal 2, readers.size
20
+
21
+ sha1 = Hash[readers.map { |rd| [ rd, Digest::SHA1.new ] } ]
22
+
23
+ readers.delete_if do |rd|
24
+ if buf = rd.read(0x10000)
25
+ sha1[rd].update buf
26
+ false
27
+ else
28
+ rd.close.nil?
29
+ end
30
+ end until readers.empty?
31
+
32
+ sha1.each_value { |dig| assert_equal EXPECT, dig.hexdigest }
33
+ sprayer.instance_variable_get(:@writers).each { |x| assert x.closed? }
34
+ end
35
+
36
+ def test_spray_one_reader_dies
37
+ sprayer = HTTP_Spew::InputSpray.new(@env, 3)
38
+ readers = sprayer.readers
39
+ assert_equal 3, readers.size
40
+ sha1 = Hash[readers.map { |rd| [ rd, Digest::SHA1.new ] } ]
41
+ count = Hash[readers.map { |rd| [ rd, 0 ] } ]
42
+ to_die = readers[0]
43
+ readers.delete_if do |rd|
44
+ if buf = rd.read(0x100)
45
+ sha1[rd].update buf
46
+ n = count[rd] += buf.size
47
+ if rd == to_die && n >= 0x20000
48
+ rd.close.nil?
49
+ else
50
+ false
51
+ end
52
+ else
53
+ rd.close.nil?
54
+ end
55
+ end until readers.empty?
56
+
57
+ dead_sha1 = sha1.delete to_die
58
+ assert EXPECT != dead_sha1.hexdigest
59
+ sha1.each_value { |dig| assert_equal EXPECT, dig.hexdigest }
60
+ sprayer.instance_variable_get(:@writers).each { |x| assert x.closed? }
61
+ end
62
+
63
+ def test_spray_stream
64
+ rd, wr = IO.pipe
65
+ assert @env.delete("CONTENT_LENGTH")
66
+ io = @env.delete("rack.input")
67
+ buf = ""
68
+ thr = Thread.new do
69
+ while buf = io.read(128, buf)
70
+ wr.write buf
71
+ end
72
+ wr.close
73
+ :ok
74
+ end
75
+ @env["rack.input"] = rd
76
+ sprayer = HTTP_Spew::InputSpray.new(@env, 2)
77
+ readers = sprayer.readers
78
+ assert_equal 2, readers.size
79
+
80
+ sha1 = Hash[readers.map { |rd| [ rd, Digest::SHA1.new ] } ]
81
+
82
+ readers.delete_if do |rd|
83
+ assert ! rd.respond_to?(:size)
84
+ if buf = rd.read(0x10000)
85
+ sha1[rd].update buf
86
+ false
87
+ else
88
+ rd.close.nil?
89
+ end
90
+ end until readers.empty?
91
+
92
+ sha1.each_value { |dig| assert_equal EXPECT, dig.hexdigest }
93
+ sprayer.instance_variable_get(:@writers).each { |x| assert x.closed? }
94
+ thr.join
95
+ assert_equal :ok, thr.value
96
+ assert ! rd.closed?
97
+ assert wr.closed?
98
+ ensure
99
+ rd.close
100
+ end
101
+ end
@@ -0,0 +1,60 @@
1
+ # -*- encoding: binary -*-
2
+ require "./test/helper"
3
+
4
+ class TestMirror < Test::Unit::TestCase
5
+ def setup
6
+ @addr, @port, @srv = start_server("./test/mirror.ru")
7
+ @sockaddr = Socket.pack_sockaddr_in(@port, @addr)
8
+ @env = {
9
+ "REQUEST_METHOD" => "PUT",
10
+ "REQUEST_URI" => "/",
11
+ "HTTP_HOST" => "example.com",
12
+ }
13
+ end
14
+
15
+ def teardown
16
+ Process.kill(:QUIT, @srv)
17
+ Process.waitpid2(@srv)
18
+ end
19
+
20
+ def test_mirror_small
21
+ str = rand_data(128)
22
+ expect = [ str ]
23
+ req = []
24
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
25
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
26
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
27
+ rv = HTTP_Spew.wait(3, req, 666000)
28
+ rv.each do |req|
29
+ assert_nil req.error
30
+ response = req.response
31
+ headers = Rack::Utils::HeaderHash.new(response[1])
32
+ assert_equal 128, headers["Content-Length"].to_i
33
+ assert_equal 200, response[0].to_i
34
+ tmp = []
35
+ response[2].each { |x| tmp << x.dup }
36
+ assert_equal expect, tmp
37
+ end
38
+ end
39
+
40
+ # no bidirectional input support
41
+ def test_mirror_big
42
+ str = rand_data(128) * (8 * 1024 * 8)
43
+ expect = str
44
+ req = []
45
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
46
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
47
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
48
+ rv = HTTP_Spew.wait(3, req, 6000)
49
+ rv.each do |req|
50
+ assert_nil req.error
51
+ response = req.response
52
+ headers = Rack::Utils::HeaderHash.new(response[1])
53
+ assert_equal str.size, headers["Content-Length"].to_i
54
+ assert_equal 200, response[0].to_i
55
+ tmp = ""
56
+ response[2].each { |x| tmp << x }
57
+ assert_equal expect, tmp
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,54 @@
1
+ require "./test/helper"
2
+
3
+ class TestRequest < Test::Unit::TestCase
4
+ def setup
5
+ @addr, @port, @srv = start_server("./test/sha1.ru", 1)
6
+ @sockaddr = Socket.pack_sockaddr_in(@port, @addr)
7
+ @env = {
8
+ "REQUEST_METHOD" => "PUT",
9
+ "REQUEST_URI" => "/",
10
+ "HTTP_HOST" => "example.com",
11
+ }
12
+ @tmpfiles = []
13
+ end
14
+
15
+ def teardown
16
+ Process.kill(:QUIT, @srv)
17
+ Process.waitpid2(@srv)
18
+ @tmpfiles.each { |tmp| tmp.closed? or tmp.close! }
19
+ end
20
+
21
+ def test_request_with_existing_socket
22
+ sock = Kgio::Socket.new(@sockaddr)
23
+ req = HTTP_Spew::Request.new(@env, nil, sock)
24
+ assert_equal sock, req.to_io
25
+ assert_nothing_raised { req.close }
26
+ assert sock.closed?
27
+ end
28
+
29
+ def test_request_single
30
+ req = HTTP_Spew::Request.new(@env, nil, @sockaddr)
31
+ sym = req.resume
32
+ assert_kind_of(Symbol, sym)
33
+ if sym == :wait_writable
34
+ set = Kgio.poll({req => sym}, 100)
35
+ assert_equal [ req ], set.keys
36
+ sym = req.resume
37
+ end
38
+ assert_equal :wait_readable, sym
39
+ set = Kgio.poll({req => sym}, 100)
40
+ assert_equal [ req ], set.keys
41
+ rv = req.resume
42
+ assert_equal req, rv[2]
43
+ end
44
+
45
+ def test_request_loop
46
+ req = HTTP_Spew::Request.new(@env, nil, @sockaddr)
47
+ until Array === (rv = req.resume)
48
+ Kgio.poll(req => rv)
49
+ end
50
+ assert_kind_of Array, rv
51
+ assert_equal 3, rv.size
52
+ assert_equal req, rv[2]
53
+ end
54
+ end
@@ -0,0 +1,127 @@
1
+ # -*- encoding: binary -*-
2
+ require "./test/helper"
3
+
4
+ class TestUpload < Test::Unit::TestCase
5
+ def setup
6
+ @addr, @port, @srv = start_server("./test/sha1.ru")
7
+ @sockaddr = Socket.pack_sockaddr_in(@port, @addr)
8
+ @env = {
9
+ "REQUEST_METHOD" => "PUT",
10
+ "REQUEST_URI" => "/",
11
+ "HTTP_HOST" => "example.com",
12
+ }
13
+ @tmpfiles = []
14
+ end
15
+
16
+ def teardown
17
+ Process.kill(:QUIT, @srv)
18
+ Process.waitpid2(@srv)
19
+ @tmpfiles.each { |tmp| tmp.closed? or tmp.close! }
20
+ end
21
+
22
+ def test_spew_upload_empty
23
+ req = []
24
+ req << HTTP_Spew::Request.new(@env, nil, @sockaddr)
25
+ req << HTTP_Spew::Request.new(@env, nil, @sockaddr)
26
+ req << HTTP_Spew::Request.new(@env, nil, @sockaddr)
27
+ rv = HTTP_Spew.wait(3, req, 666000)
28
+ assert_equal 3, rv.size
29
+ rv.each do |req|
30
+ assert_nil req.error
31
+ response = req.response
32
+ assert_equal 200, response[0].to_i
33
+ tmp = []
34
+ response[2].each { |x| tmp << x.dup }
35
+ assert_equal [ "da39a3ee5e6b4b0d3255bfef95601890afd80709\n" ], tmp
36
+ end
37
+ end
38
+
39
+ def tmp_blob(str)
40
+ tmp = Tempfile.new "blob"
41
+ tmp.write str
42
+ tmp.flush
43
+ assert_equal str.size, tmp.size
44
+ tmp.rewind
45
+ @tmpfiles << tmp
46
+ tmp
47
+ end
48
+
49
+ def test_spew_upload_big
50
+ str = rand_data(128) * (8 * 1024 * 4)
51
+ expect = [ Digest::SHA1.hexdigest(str) << "\n" ]
52
+ req = []
53
+ req << HTTP_Spew::Request.new(@env, tmp_blob(str), @sockaddr)
54
+ req << HTTP_Spew::Request.new(@env, tmp_blob(str), @sockaddr)
55
+ req << HTTP_Spew::Request.new(@env, tmp_blob(str), @sockaddr)
56
+ rv = HTTP_Spew.wait(3, req, 666000)
57
+ assert_equal 3, rv.size
58
+ rv.each do |req|
59
+ assert_nil req.error
60
+ response = req.response
61
+ assert_equal 200, response[0].to_i
62
+ tmp = []
63
+ response[2].each { |x| tmp << x.dup }
64
+ assert_equal expect, tmp
65
+ end
66
+ end
67
+
68
+ def test_spew_upload_small
69
+ str = rand_data(128)
70
+ expect = [ Digest::SHA1.hexdigest(str) << "\n" ]
71
+ req = []
72
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
73
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
74
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
75
+ rv = HTTP_Spew.wait(3, req, 666000)
76
+ assert_equal 3, rv.size
77
+ rv.each do |req|
78
+ assert_nil req.error
79
+ response = req.response
80
+ assert_equal 200, response[0].to_i
81
+ tmp = []
82
+ response[2].each { |x| tmp << x.dup }
83
+ assert_equal expect, tmp
84
+ end
85
+ end
86
+
87
+ def test_spew_upload_small_two_of_three
88
+ str = rand_data(128)
89
+ expect = [ Digest::SHA1.hexdigest(str) << "\n" ]
90
+ req = []
91
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
92
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
93
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
94
+ rv = HTTP_Spew.wait(2, req, 666000)
95
+ assert_equal 3, rv.size
96
+ rv[0, 2].each do |req|
97
+ assert_nil req.error
98
+ response = req.response
99
+ assert_equal 200, response[0].to_i
100
+ tmp = []
101
+ response[2].each { |x| tmp << x.dup }
102
+ assert_equal expect, tmp
103
+ assert_nothing_raised { response[2].close }
104
+ assert req.to_io.closed?
105
+ end
106
+ failed = rv[2]
107
+ assert_kind_of(HTTP_Spew::ConnectionReset, failed.error)
108
+ assert failed.to_io.closed?
109
+ end
110
+
111
+ def test_spew_upload_nonblock
112
+ str = rand_data(128)
113
+ req = []
114
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
115
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
116
+ req << HTTP_Spew::Request.new(@env, StringIO.new(str), @sockaddr)
117
+ before = req.dup
118
+ rv = HTTP_Spew.wait_nonblock!(3, req)
119
+ assert_nil rv
120
+ while rv.nil? do
121
+ rv = HTTP_Spew.wait_nonblock!(3, req)
122
+ end
123
+ assert_nil rv.uniq!
124
+ assert rv.size > 0
125
+ rv.each { |req| assert before.include?(req) }
126
+ end
127
+ end