ebb 0.2.1 → 0.3.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/README +10 -44
- data/Rakefile +123 -0
- data/ext/ebb.c +794 -0
- data/ext/ebb.h +130 -0
- data/ext/ebb_ffi.c +575 -0
- data/ext/ebb_request_parser.c +5339 -0
- data/ext/ebb_request_parser.h +97 -0
- data/ext/ebb_request_parser.rl +513 -0
- data/{src → ext}/extconf.rb +12 -8
- data/ext/rbtree.c +408 -0
- data/ext/rbtree.h +54 -0
- data/lib/ebb.rb +311 -0
- data/lib/ebb/version.rb +4 -0
- data/libev/ev++.h +803 -0
- data/libev/ev.c +24 -6
- data/libev/ev.h +4 -0
- data/libev/ev_select.c +50 -15
- data/libev/ev_vars.h +3 -0
- data/libev/ev_win32.c +3 -0
- data/libev/ev_wrap.h +2 -0
- data/libev/event.c +403 -0
- data/libev/event.h +152 -0
- metadata +26 -40
- data/benchmark/application.rb +0 -93
- data/benchmark/server_test.rb +0 -193
- data/bin/ebb_rails +0 -4
- data/ruby_lib/ebb.rb +0 -257
- data/ruby_lib/ebb/runner.rb +0 -134
- data/ruby_lib/ebb/runner/rails.rb +0 -31
- data/ruby_lib/rack/adapter/rails.rb +0 -159
- data/src/ebb.c +0 -627
- data/src/ebb.h +0 -102
- data/src/ebb_ruby.c +0 -306
- data/src/parser.c +0 -2860
- data/src/parser.h +0 -53
- data/test/basic_test.rb +0 -46
- data/test/ebb_rails_test.rb +0 -34
- data/test/env_test.rb +0 -110
- data/test/helper.rb +0 -138
data/src/parser.h
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* Copyright (c) 2005 Zed A. Shaw
|
3
|
-
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
-
*/
|
5
|
-
|
6
|
-
#ifndef http11_parser_h
|
7
|
-
#define http11_parser_h
|
8
|
-
|
9
|
-
#include <sys/types.h>
|
10
|
-
|
11
|
-
#if defined(_WIN32)
|
12
|
-
#include <stddef.h>
|
13
|
-
#endif
|
14
|
-
|
15
|
-
enum { MONGREL_CONTENT_LENGTH
|
16
|
-
, MONGREL_CONTENT_TYPE
|
17
|
-
, MONGREL_FRAGMENT
|
18
|
-
, MONGREL_HTTP_VERSION
|
19
|
-
, MONGREL_QUERY_STRING
|
20
|
-
, MONGREL_REQUEST_PATH
|
21
|
-
, MONGREL_REQUEST_METHOD
|
22
|
-
, MONGREL_REQUEST_URI
|
23
|
-
};
|
24
|
-
|
25
|
-
typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen);
|
26
|
-
typedef void (*element_cb)(void *data, int type, const char *at, size_t length);
|
27
|
-
|
28
|
-
typedef struct http_parser {
|
29
|
-
int cs;
|
30
|
-
int overflow_error;
|
31
|
-
size_t body_start;
|
32
|
-
size_t content_length;
|
33
|
-
size_t nread;
|
34
|
-
size_t mark;
|
35
|
-
size_t field_start;
|
36
|
-
size_t field_len;
|
37
|
-
size_t query_start;
|
38
|
-
|
39
|
-
void *data;
|
40
|
-
|
41
|
-
field_cb http_field;
|
42
|
-
element_cb on_element;
|
43
|
-
} http_parser;
|
44
|
-
|
45
|
-
void http_parser_init(http_parser *parser);
|
46
|
-
int http_parser_finish(http_parser *parser);
|
47
|
-
size_t http_parser_execute(http_parser *parser, const char *data, size_t len, size_t off);
|
48
|
-
int http_parser_has_error(http_parser *parser);
|
49
|
-
int http_parser_is_finished(http_parser *parser);
|
50
|
-
|
51
|
-
#define http_parser_nread(parser) (parser)->nread
|
52
|
-
|
53
|
-
#endif
|
data/test/basic_test.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/helper'
|
2
|
-
|
3
|
-
module BasicTests
|
4
|
-
def test_get_bytes
|
5
|
-
[1,10,1000].each do |i|
|
6
|
-
response = get("/bytes/#{i}")
|
7
|
-
assert_equal "#{'C'*i.to_i}", response['output']
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def test_get_unknown
|
12
|
-
response = get('/blah')
|
13
|
-
assert_equal "Undefined url", response['output']
|
14
|
-
end
|
15
|
-
|
16
|
-
def test_small_posts
|
17
|
-
[1,10,321,123,1000].each do |i|
|
18
|
-
response = post("/test_post_length", 'C'*i)
|
19
|
-
assert_equal 200, response['status']
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_large_post
|
24
|
-
[50,60,100].each do |i|
|
25
|
-
response = post("/test_post_length", 'C'*1024*i)
|
26
|
-
assert_equal 200, response['status']
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
class BasicTest < ServerTest
|
32
|
-
include BasicTests
|
33
|
-
end
|
34
|
-
|
35
|
-
class BasicTestFD < ServerTestFD
|
36
|
-
include BasicTests
|
37
|
-
end
|
38
|
-
|
39
|
-
class BasicTestUnixSocket < ServerTestSocket
|
40
|
-
include BasicTests
|
41
|
-
|
42
|
-
def test_socket_file_exists
|
43
|
-
assert File.exists?(@socketfile)
|
44
|
-
assert File.readable?(@socketfile)
|
45
|
-
end
|
46
|
-
end
|
data/test/ebb_rails_test.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/helper'
|
2
|
-
|
3
|
-
APP_DIR = File.dirname(__FILE__) + "/rails_app"
|
4
|
-
EBB_RAILS = "#{Ebb::LIBDIR}/../bin/ebb_rails"
|
5
|
-
class EbbRailsTest < Test::Unit::TestCase
|
6
|
-
# just to make sure there isn't some load error
|
7
|
-
def test_version
|
8
|
-
out = %x{ruby #{EBB_RAILS} -v}
|
9
|
-
assert_match %r{Ebb #{Ebb::VERSION}}, out
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_parser
|
13
|
-
runner = Ebb::Runner::Rails.new
|
14
|
-
runner.parse_options("start -c #{APP_DIR} -p #{TEST_PORT}".split)
|
15
|
-
assert_equal TEST_PORT, runner.options[:port].to_i
|
16
|
-
assert_equal APP_DIR, runner.options[:root]
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
def test_start_app
|
21
|
-
Thread.new do
|
22
|
-
runner = Ebb::Runner::Rails.new
|
23
|
-
runner.run("start -c #{APP_DIR} -p #{TEST_PORT}".split)
|
24
|
-
end
|
25
|
-
sleep 0.1 until Ebb.running?
|
26
|
-
|
27
|
-
response = get '/'
|
28
|
-
assert_equal 200, response.code.to_i
|
29
|
-
|
30
|
-
ensure
|
31
|
-
Ebb.stop_server
|
32
|
-
sleep 0.1 while Ebb.running?
|
33
|
-
end
|
34
|
-
end
|
data/test/env_test.rb
DELETED
@@ -1,110 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/helper'
|
2
|
-
require 'socket'
|
3
|
-
require 'rubygems'
|
4
|
-
require 'json'
|
5
|
-
require 'test/unit'
|
6
|
-
require 'digest/sha1'
|
7
|
-
|
8
|
-
def send_request(request_string)
|
9
|
-
socket = TCPSocket.new("0.0.0.0", TEST_PORT)
|
10
|
-
socket.write(request_string)
|
11
|
-
lines = []
|
12
|
-
out = socket.read(5000000)
|
13
|
-
raise "Connection Closed on #{request_string.inspect}" if out.nil?
|
14
|
-
out.each_line { |l| lines << l }
|
15
|
-
env = JSON.parse(lines.last)
|
16
|
-
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE
|
17
|
-
return :fail
|
18
|
-
rescue RuntimeError => e
|
19
|
-
if e.message =~ /Connection Closed/
|
20
|
-
return :fail
|
21
|
-
else
|
22
|
-
raise e
|
23
|
-
end
|
24
|
-
rescue => e
|
25
|
-
puts "unknown exception: #{e.class}"
|
26
|
-
raise e
|
27
|
-
ensure
|
28
|
-
socket.close unless socket.nil?
|
29
|
-
end
|
30
|
-
|
31
|
-
def drops_request?(request_string)
|
32
|
-
:fail == send_request(request_string)
|
33
|
-
end
|
34
|
-
|
35
|
-
class HttpParserTest < ServerTest
|
36
|
-
|
37
|
-
def test_parse_simple
|
38
|
-
env = send_request("GET / HTTP/1.0\r\n\r\n")
|
39
|
-
|
40
|
-
assert_equal 'HTTP/1.1', env['SERVER_PROTOCOL']
|
41
|
-
assert_equal '/', env['REQUEST_PATH']
|
42
|
-
assert_equal 'HTTP/1.0', env['HTTP_VERSION']
|
43
|
-
assert_equal '/', env['REQUEST_URI']
|
44
|
-
assert_equal 'GET', env['REQUEST_METHOD']
|
45
|
-
assert_nil env['FRAGMENT']
|
46
|
-
assert_nil env['QUERY_STRING']
|
47
|
-
assert_equal "", env['rack.input']
|
48
|
-
assert_equal '127.0.0.1', env['HTTP_CLIENT_IP']
|
49
|
-
end
|
50
|
-
|
51
|
-
def test_parse_dumbfuck_headers
|
52
|
-
should_be_good = "GET / HTTP/1.0\r\naaaaaaaaaaaaa:++++++++++\r\n\r\n"
|
53
|
-
env = send_request(should_be_good)
|
54
|
-
assert_equal "++++++++++", env["HTTP_AAAAAAAAAAAAA"]
|
55
|
-
assert_equal "", env['rack.input']
|
56
|
-
|
57
|
-
nasty_pound_header = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"
|
58
|
-
assert drops_request?(nasty_pound_header) # Correct?
|
59
|
-
end
|
60
|
-
|
61
|
-
def test_parse_error
|
62
|
-
assert drops_request?("GET / SsUTF/1.1")
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_fragment_in_uri
|
66
|
-
env = send_request("GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.0\r\n\r\n")
|
67
|
-
assert_equal '/forums/1/topics/2375?page=1', env['REQUEST_URI']
|
68
|
-
assert_equal 'posts-17408', env['FRAGMENT']
|
69
|
-
assert_equal "", env['rack.input']
|
70
|
-
end
|
71
|
-
|
72
|
-
# lame random garbage maker
|
73
|
-
def rand_data(min, max, readable=true)
|
74
|
-
count = min + ((rand(max)+1) *10).to_i
|
75
|
-
res = count.to_s + "/"
|
76
|
-
|
77
|
-
if readable
|
78
|
-
res << Digest::SHA1.hexdigest(rand(count * 100).to_s) * (count / 40)
|
79
|
-
else
|
80
|
-
res << Digest::SHA1.digest(rand(count * 100).to_s) * (count / 20)
|
81
|
-
end
|
82
|
-
|
83
|
-
return res
|
84
|
-
end
|
85
|
-
|
86
|
-
def test_horrible_queries
|
87
|
-
10.times do |c|
|
88
|
-
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-#{rand_data(1024, 1024+(c*1024))}: Test\r\n\r\n"
|
89
|
-
assert drops_request?(req), "large header names are caught"
|
90
|
-
end
|
91
|
-
|
92
|
-
# then that large mangled field values are caught
|
93
|
-
10.times do |c|
|
94
|
-
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\nX-Test: #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
95
|
-
assert drops_request?(req), "large mangled field values are caught"
|
96
|
-
### XXX this is broken! fix me. this test should drop the request.
|
97
|
-
end
|
98
|
-
|
99
|
-
# then large headers are rejected too
|
100
|
-
req = "GET /#{rand_data(10,120)} HTTP/1.1\r\n"
|
101
|
-
req << "X-Test: test\r\n" * (80 * 1024)
|
102
|
-
assert drops_request?(req), "large headers are rejected"
|
103
|
-
|
104
|
-
# finally just that random garbage gets blocked all the time
|
105
|
-
10.times do |c|
|
106
|
-
req = "GET #{rand_data(1024, 1024+(c*1024), false)} #{rand_data(1024, 1024+(c*1024), false)}\r\n\r\n"
|
107
|
-
assert drops_request?(req), "random garbage gets blocked all the time"
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
data/test/helper.rb
DELETED
@@ -1,138 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require File.dirname(__FILE__) + '/../ruby_lib/ebb'
|
3
|
-
require 'test/unit'
|
4
|
-
require 'net/http'
|
5
|
-
require 'socket'
|
6
|
-
require 'rubygems'
|
7
|
-
require 'json'
|
8
|
-
|
9
|
-
|
10
|
-
Ebb.log = File.open('/dev/null','w')
|
11
|
-
|
12
|
-
TEST_PORT = 4044
|
13
|
-
|
14
|
-
|
15
|
-
class HelperApp
|
16
|
-
def call(env)
|
17
|
-
commands = env['PATH_INFO'].split('/')
|
18
|
-
|
19
|
-
if commands.include?('bytes')
|
20
|
-
n = commands.last.to_i
|
21
|
-
raise "bytes called with n <= 0" if n <= 0
|
22
|
-
body = "C"*n
|
23
|
-
status = 200
|
24
|
-
|
25
|
-
elsif commands.include?('test_post_length')
|
26
|
-
input_body = env['rack.input'].read
|
27
|
-
|
28
|
-
content_length_header = env['CONTENT_LENGTH'].to_i
|
29
|
-
|
30
|
-
if content_length_header == input_body.length
|
31
|
-
body = "Content-Length matches input length"
|
32
|
-
status = 200
|
33
|
-
else
|
34
|
-
body = "Content-Length header is #{content_length_header} but body length is #{input_body.length}"
|
35
|
-
status = 500
|
36
|
-
end
|
37
|
-
|
38
|
-
else
|
39
|
-
status = 404
|
40
|
-
body = "Undefined url"
|
41
|
-
end
|
42
|
-
|
43
|
-
env['rack.input'] = env['rack.input'].read
|
44
|
-
env.delete('rack.errors')
|
45
|
-
env['output'] = body
|
46
|
-
env['status'] = status
|
47
|
-
|
48
|
-
[status, {'Content-Type' => 'text/json'}, env.to_json]
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
class Test::Unit::TestCase
|
53
|
-
def get(path)
|
54
|
-
response = Net::HTTP.get_response(URI.parse("http://0.0.0.0:#{TEST_PORT}#{path}"))
|
55
|
-
end
|
56
|
-
|
57
|
-
def post(path, data)
|
58
|
-
response = Net::HTTP.post_form(URI.parse("http://0.0.0.0:#{TEST_PORT}#{path}"), data)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class ServerTest < Test::Unit::TestCase
|
63
|
-
def get(path)
|
64
|
-
response = Net::HTTP.get_response(URI.parse("http://0.0.0.0:#{TEST_PORT}#{path}"))
|
65
|
-
env = JSON.parse(response.body)
|
66
|
-
end
|
67
|
-
|
68
|
-
def post(path, data)
|
69
|
-
response = Net::HTTP.post_form(URI.parse("http://0.0.0.0:#{TEST_PORT}#{path}"), data)
|
70
|
-
env = JSON.parse(response.body)
|
71
|
-
end
|
72
|
-
|
73
|
-
def setup
|
74
|
-
Thread.new { Ebb.start_server(HelperApp.new, :port => TEST_PORT) }
|
75
|
-
sleep 0.1 until Ebb.running?
|
76
|
-
end
|
77
|
-
|
78
|
-
def teardown
|
79
|
-
Ebb.stop_server
|
80
|
-
sleep 0.1 while Ebb.running?
|
81
|
-
end
|
82
|
-
|
83
|
-
def default_test
|
84
|
-
assert true
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
class ServerTestFD < ServerTest
|
89
|
-
def setup
|
90
|
-
@tcp_server = TCPServer.new('0.0.0.0', TEST_PORT);
|
91
|
-
Thread.new { Ebb.start_server(HelperApp.new, :fileno => @tcp_server.fileno) }
|
92
|
-
sleep 0.1 until Ebb.running?
|
93
|
-
end
|
94
|
-
|
95
|
-
def teardown
|
96
|
-
super
|
97
|
-
@tcp_server = nil
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
class ServerTestSocket < ServerTest
|
102
|
-
def get(path)
|
103
|
-
socket = UNIXSocket.open(@socketfile)
|
104
|
-
socket.write("GET #{path} HTTP/1.0\r\n\r\n")
|
105
|
-
response = ""
|
106
|
-
while chunk = socket.read(100)
|
107
|
-
response << chunk
|
108
|
-
end
|
109
|
-
body = response.split("\r\n\r\n")[1]
|
110
|
-
env = JSON.parse(body)
|
111
|
-
ensure
|
112
|
-
socket.close if socket
|
113
|
-
end
|
114
|
-
|
115
|
-
def post(path, data)
|
116
|
-
socket = UNIXSocket.open(@socketfile)
|
117
|
-
socket.write("POST #{path} HTTP/1.0\r\nContent-Length: #{data.length}\r\n\r\n#{data}")
|
118
|
-
response = ""
|
119
|
-
while chunk = socket.read(100)
|
120
|
-
response << chunk
|
121
|
-
end
|
122
|
-
body = response.split("\r\n\r\n")[1]
|
123
|
-
env = JSON.parse(body)
|
124
|
-
ensure
|
125
|
-
socket.close if socket
|
126
|
-
end
|
127
|
-
|
128
|
-
def setup
|
129
|
-
@socketfile = '/tmp/ebb_unittest.sock'
|
130
|
-
Thread.new { Ebb.start_server(HelperApp.new, :unix_socket => @socketfile) }
|
131
|
-
sleep 0.1 until Ebb.running?
|
132
|
-
end
|
133
|
-
|
134
|
-
def teardown
|
135
|
-
super
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|