tipi 0.41 → 0.46
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/test.yml +3 -1
- data/.gitignore +3 -1
- data/CHANGELOG.md +34 -0
- data/Gemfile +7 -1
- data/Gemfile.lock +53 -33
- data/README.md +184 -8
- data/Rakefile +1 -7
- data/benchmarks/bm_http1_parser.rb +85 -0
- data/bin/benchmark +37 -0
- data/bin/h1pd +6 -0
- data/bin/tipi +3 -21
- data/bm.png +0 -0
- data/df/agent.rb +1 -1
- data/df/sample_agent.rb +2 -2
- data/df/server.rb +3 -1
- data/df/server_utils.rb +48 -46
- data/examples/full_service.rb +13 -0
- data/examples/hello.rb +5 -0
- data/examples/hello.ru +3 -3
- data/examples/http1_parser.rb +10 -8
- data/examples/http_server.js +1 -1
- data/examples/http_server.rb +4 -1
- data/examples/http_server_graceful.rb +1 -1
- data/examples/https_server.rb +41 -15
- data/examples/rack_server_forked.rb +26 -0
- data/examples/rack_server_https_forked.rb +1 -1
- data/examples/servername_cb.rb +37 -0
- data/examples/websocket_demo.rb +1 -1
- data/lib/tipi/acme.rb +320 -0
- data/lib/tipi/cli.rb +93 -0
- data/lib/tipi/config_dsl.rb +13 -13
- data/lib/tipi/configuration.rb +2 -2
- data/lib/tipi/controller/bare_polyphony.rb +0 -0
- data/lib/tipi/controller/bare_stock.rb +10 -0
- data/lib/tipi/controller/extensions.rb +37 -0
- data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
- data/lib/tipi/controller/web_polyphony.rb +353 -0
- data/lib/tipi/controller/web_stock.rb +635 -0
- data/lib/tipi/controller.rb +12 -0
- data/lib/tipi/digital_fabric/agent.rb +5 -5
- data/lib/tipi/digital_fabric/agent_proxy.rb +15 -8
- data/lib/tipi/digital_fabric/executive.rb +7 -3
- data/lib/tipi/digital_fabric/protocol.rb +3 -3
- data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
- data/lib/tipi/digital_fabric/service.rb +17 -18
- data/lib/tipi/handler.rb +2 -2
- data/lib/tipi/http1_adapter.rb +85 -124
- data/lib/tipi/http2_adapter.rb +29 -16
- data/lib/tipi/http2_stream.rb +52 -57
- data/lib/tipi/rack_adapter.rb +2 -2
- data/lib/tipi/response_extensions.rb +1 -1
- data/lib/tipi/supervisor.rb +75 -0
- data/lib/tipi/version.rb +1 -1
- data/lib/tipi/websocket.rb +3 -3
- data/lib/tipi.rb +9 -7
- data/test/coverage.rb +2 -2
- data/test/helper.rb +60 -12
- data/test/test_http_server.rb +14 -41
- data/test/test_request.rb +2 -29
- data/tipi.gemspec +10 -10
- metadata +80 -54
- data/examples/automatic_certificate.rb +0 -193
- data/ext/tipi/extconf.rb +0 -12
- data/ext/tipi/http1_parser.c +0 -534
- data/ext/tipi/http1_parser.h +0 -18
- data/ext/tipi/tipi_ext.c +0 -5
- data/lib/tipi/http1_adapter_new.rb +0 -293
data/lib/tipi/http2_adapter.rb
CHANGED
@@ -3,18 +3,30 @@
|
|
3
3
|
require 'http/2'
|
4
4
|
require_relative './http2_stream'
|
5
5
|
|
6
|
+
# patch to fix bug in HTTP2::Stream
|
7
|
+
class HTTP2::Stream
|
8
|
+
def end_stream?(frame)
|
9
|
+
case frame[:type]
|
10
|
+
when :data, :headers, :continuation
|
11
|
+
frame[:flags]&.include?(:end_stream)
|
12
|
+
else false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
6
17
|
module Tipi
|
7
18
|
# HTTP2 server adapter
|
8
19
|
class HTTP2Adapter
|
9
|
-
def self.upgrade_each(socket, opts, headers, &block)
|
10
|
-
adapter = new(socket, opts, headers)
|
20
|
+
def self.upgrade_each(socket, opts, headers, body, &block)
|
21
|
+
adapter = new(socket, opts, headers, body)
|
11
22
|
adapter.each(&block)
|
12
23
|
end
|
13
|
-
|
14
|
-
def initialize(conn, opts, upgrade_headers = nil)
|
24
|
+
|
25
|
+
def initialize(conn, opts, upgrade_headers = nil, upgrade_body = nil)
|
15
26
|
@conn = conn
|
16
27
|
@opts = opts
|
17
28
|
@upgrade_headers = upgrade_headers
|
29
|
+
@upgrade_body = upgrade_body
|
18
30
|
@first = true
|
19
31
|
@rx = (upgrade_headers && upgrade_headers[':rx']) || 0
|
20
32
|
@tx = (upgrade_headers && upgrade_headers[':tx']) || 0
|
@@ -24,33 +36,34 @@ module Tipi
|
|
24
36
|
@interface.on(:frame, &method(:send_frame))
|
25
37
|
@streams = {}
|
26
38
|
end
|
27
|
-
|
39
|
+
|
28
40
|
def send_frame(data)
|
29
41
|
if @transfer_count_request
|
30
42
|
@transfer_count_request.tx_incr(data.bytesize)
|
31
43
|
end
|
32
44
|
@conn << data
|
45
|
+
rescue Polyphony::BaseException
|
46
|
+
raise
|
33
47
|
rescue Exception => e
|
34
48
|
@connection_fiber.transfer e
|
35
49
|
end
|
36
|
-
|
50
|
+
|
37
51
|
UPGRADE_MESSAGE = <<~HTTP.gsub("\n", "\r\n")
|
38
52
|
HTTP/1.1 101 Switching Protocols
|
39
53
|
Connection: Upgrade
|
40
54
|
Upgrade: h2c
|
41
|
-
|
55
|
+
|
42
56
|
HTTP
|
43
|
-
|
57
|
+
|
44
58
|
def upgrade
|
45
59
|
@conn << UPGRADE_MESSAGE
|
46
60
|
@tx += UPGRADE_MESSAGE.bytesize
|
47
61
|
settings = @upgrade_headers['http2-settings']
|
48
|
-
|
49
|
-
@interface.upgrade(settings, @upgrade_headers, '')
|
62
|
+
@interface.upgrade(settings, @upgrade_headers, @upgrade_body || '')
|
50
63
|
ensure
|
51
64
|
@upgrade_headers = nil
|
52
65
|
end
|
53
|
-
|
66
|
+
|
54
67
|
# Iterates over incoming requests
|
55
68
|
def each(&block)
|
56
69
|
@interface.on(:stream) { |stream| start_stream(stream, &block) }
|
@@ -60,7 +73,7 @@ module Tipi
|
|
60
73
|
@rx += data.bytesize
|
61
74
|
@interface << data
|
62
75
|
end
|
63
|
-
rescue SystemCallError, IOError
|
76
|
+
rescue SystemCallError, IOError, HTTP2::Error::Error
|
64
77
|
# ignore
|
65
78
|
ensure
|
66
79
|
finalize_client_loop
|
@@ -71,26 +84,26 @@ module Tipi
|
|
71
84
|
@rx = 0
|
72
85
|
count
|
73
86
|
end
|
74
|
-
|
87
|
+
|
75
88
|
def get_tx_count
|
76
89
|
count = @tx
|
77
90
|
@tx = 0
|
78
91
|
count
|
79
92
|
end
|
80
|
-
|
93
|
+
|
81
94
|
def start_stream(stream, &block)
|
82
95
|
stream = HTTP2StreamHandler.new(self, stream, @conn, @first, &block)
|
83
96
|
@first = nil if @first
|
84
97
|
@streams[stream] = true
|
85
98
|
end
|
86
|
-
|
99
|
+
|
87
100
|
def finalize_client_loop
|
88
101
|
@interface = nil
|
89
102
|
@streams.each_key(&:stop)
|
90
103
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
91
104
|
@conn.close
|
92
105
|
end
|
93
|
-
|
106
|
+
|
94
107
|
def close
|
95
108
|
@conn.shutdown if @conn.respond_to?(:shutdown) rescue nil
|
96
109
|
@conn.close
|
data/lib/tipi/http2_stream.rb
CHANGED
@@ -6,17 +6,15 @@ require 'qeweney/request'
|
|
6
6
|
module Tipi
|
7
7
|
# Manages an HTTP 2 stream
|
8
8
|
class HTTP2StreamHandler
|
9
|
-
attr_accessor :__next__
|
10
9
|
attr_reader :conn
|
11
|
-
|
10
|
+
|
12
11
|
def initialize(adapter, stream, conn, first, &block)
|
13
12
|
@adapter = adapter
|
14
13
|
@stream = stream
|
15
14
|
@conn = conn
|
16
15
|
@first = first
|
17
16
|
@connection_fiber = Fiber.current
|
18
|
-
@stream_fiber = spin {
|
19
|
-
Thread.current.fiber_unschedule(@stream_fiber)
|
17
|
+
@stream_fiber = spin { run(&block) }
|
20
18
|
|
21
19
|
# Stream callbacks occur on the connection fiber (see HTTP2Adapter#each).
|
22
20
|
# The request handler is run on a separate fiber for each stream, allowing
|
@@ -33,20 +31,20 @@ module Tipi
|
|
33
31
|
stream.on(:data, &method(:on_data))
|
34
32
|
stream.on(:half_close, &method(:on_half_close))
|
35
33
|
end
|
36
|
-
|
37
|
-
def
|
34
|
+
|
35
|
+
def run(&block)
|
36
|
+
request = receive
|
38
37
|
error = nil
|
39
38
|
block.(request)
|
40
39
|
@connection_fiber.schedule
|
41
|
-
rescue Polyphony::
|
42
|
-
|
40
|
+
rescue Polyphony::BaseException
|
41
|
+
raise
|
43
42
|
rescue Exception => e
|
44
43
|
error = e
|
45
44
|
ensure
|
46
|
-
@done = true
|
47
45
|
@connection_fiber.schedule error
|
48
46
|
end
|
49
|
-
|
47
|
+
|
50
48
|
def on_headers(headers)
|
51
49
|
@request = Qeweney::Request.new(headers.to_h, self)
|
52
50
|
@request.rx_incr(@adapter.get_rx_count)
|
@@ -55,31 +53,21 @@ module Tipi
|
|
55
53
|
@request.headers[':first'] = true
|
56
54
|
@first = false
|
57
55
|
end
|
58
|
-
@stream_fiber
|
56
|
+
@stream_fiber << @request
|
59
57
|
end
|
60
58
|
|
61
59
|
def on_data(data)
|
62
60
|
data = data.to_s # chunks might be wrapped in a HTTP2::Buffer
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
else
|
67
|
-
@request.buffer_body_chunk(data)
|
68
|
-
end
|
61
|
+
|
62
|
+
(@buffered_chunks ||= []) << data
|
63
|
+
@get_body_chunk_fiber&.schedule
|
69
64
|
end
|
70
65
|
|
71
66
|
def on_half_close
|
72
|
-
|
73
|
-
|
74
|
-
@stream_fiber.schedule
|
75
|
-
elsif @waiting_for_half_close
|
76
|
-
@waiting_for_half_close = nil
|
77
|
-
@stream_fiber.schedule
|
78
|
-
else
|
79
|
-
@request.complete!
|
80
|
-
end
|
67
|
+
@get_body_chunk_fiber&.schedule
|
68
|
+
@complete = true
|
81
69
|
end
|
82
|
-
|
70
|
+
|
83
71
|
def protocol
|
84
72
|
'h2'
|
85
73
|
end
|
@@ -90,33 +78,40 @@ module Tipi
|
|
90
78
|
ensure
|
91
79
|
@adapter.unset_request_for_transfer_count(request)
|
92
80
|
end
|
93
|
-
|
94
|
-
def get_body_chunk(request)
|
95
|
-
|
96
|
-
return
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
# resumed
|
81
|
+
|
82
|
+
def get_body_chunk(request, buffered_only = false)
|
83
|
+
@buffered_chunks ||= []
|
84
|
+
return @buffered_chunks.shift unless @buffered_chunks.empty?
|
85
|
+
return nil if @complete
|
86
|
+
|
87
|
+
begin
|
88
|
+
@get_body_chunk_fiber = Fiber.current
|
102
89
|
suspend
|
90
|
+
ensure
|
91
|
+
@get_body_chunk_fiber = nil
|
103
92
|
end
|
104
|
-
|
105
|
-
@waiting_for_body_chunk = nil
|
93
|
+
@buffered_chunks.shift
|
106
94
|
end
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
return if @
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
95
|
+
|
96
|
+
def get_body(request)
|
97
|
+
@buffered_chunks ||= []
|
98
|
+
return @buffered_chunks.join if @complete
|
99
|
+
|
100
|
+
while !@complete
|
101
|
+
begin
|
102
|
+
@get_body_chunk_fiber = Fiber.current
|
103
|
+
suspend
|
104
|
+
ensure
|
105
|
+
@get_body_chunk_fiber = nil
|
106
|
+
end
|
115
107
|
end
|
116
|
-
|
117
|
-
|
108
|
+
@buffered_chunks.join
|
109
|
+
end
|
110
|
+
|
111
|
+
def complete?(request)
|
112
|
+
@complete
|
118
113
|
end
|
119
|
-
|
114
|
+
|
120
115
|
# response API
|
121
116
|
def respond(request, chunk, headers)
|
122
117
|
headers[':status'] ||= Qeweney::Status::OK
|
@@ -153,10 +148,10 @@ module Tipi
|
|
153
148
|
end
|
154
149
|
end
|
155
150
|
end
|
156
|
-
|
151
|
+
|
157
152
|
def send_headers(request, headers, empty_response: false)
|
158
153
|
return if @headers_sent
|
159
|
-
|
154
|
+
|
160
155
|
headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
|
161
156
|
with_transfer_count(request) do
|
162
157
|
@stream.headers(transform_headers(headers), end_stream: false)
|
@@ -165,10 +160,10 @@ module Tipi
|
|
165
160
|
rescue HTTP2::Error::StreamClosed
|
166
161
|
# ignore
|
167
162
|
end
|
168
|
-
|
163
|
+
|
169
164
|
def send_chunk(request, chunk, done: false)
|
170
165
|
send_headers({}, false) unless @headers_sent
|
171
|
-
|
166
|
+
|
172
167
|
if chunk
|
173
168
|
with_transfer_count(request) do
|
174
169
|
@stream.data(chunk, end_stream: done)
|
@@ -179,7 +174,7 @@ module Tipi
|
|
179
174
|
rescue HTTP2::Error::StreamClosed
|
180
175
|
# ignore
|
181
176
|
end
|
182
|
-
|
177
|
+
|
183
178
|
def finish(request)
|
184
179
|
if @headers_sent
|
185
180
|
@stream.close
|
@@ -192,10 +187,10 @@ module Tipi
|
|
192
187
|
rescue HTTP2::Error::StreamClosed
|
193
188
|
# ignore
|
194
189
|
end
|
195
|
-
|
190
|
+
|
196
191
|
def stop
|
197
|
-
return if @
|
198
|
-
|
192
|
+
return if @complete
|
193
|
+
|
199
194
|
@stream.close
|
200
195
|
@stream_fiber.schedule(Polyphony::MoveOn.new)
|
201
196
|
end
|
data/lib/tipi/rack_adapter.rb
CHANGED
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'polyphony'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Tipi
|
7
|
+
module Supervisor
|
8
|
+
class << self
|
9
|
+
def run(opts)
|
10
|
+
puts "Start supervisor pid: #{Process.pid}"
|
11
|
+
@opts = opts
|
12
|
+
@controller_watcher = start_controller_watcher
|
13
|
+
supervise_loop
|
14
|
+
end
|
15
|
+
|
16
|
+
def start_controller_watcher
|
17
|
+
spin do
|
18
|
+
cmd = controller_cmd
|
19
|
+
puts "Starting controller..."
|
20
|
+
pid = Kernel.spawn(*cmd)
|
21
|
+
@controller_pid = pid
|
22
|
+
puts "Controller pid: #{pid}"
|
23
|
+
_pid, status = Polyphony.backend_waitpid(pid)
|
24
|
+
puts "Controller has terminated with status: #{status.inspect}"
|
25
|
+
terminated = true
|
26
|
+
ensure
|
27
|
+
if pid && !terminated
|
28
|
+
puts "Terminate controller #{pid.inspect}"
|
29
|
+
Polyphony::Process.kill_process(pid)
|
30
|
+
end
|
31
|
+
Fiber.current.parent << pid
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def controller_cmd
|
36
|
+
[
|
37
|
+
'ruby',
|
38
|
+
File.join(__dir__, 'controller.rb'),
|
39
|
+
@opts.to_json
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def supervise_loop
|
44
|
+
this_fiber = Fiber.current
|
45
|
+
trap('SIGUSR2') { this_fiber << :replace_controller }
|
46
|
+
loop do
|
47
|
+
case (msg = receive)
|
48
|
+
when :replace_controller
|
49
|
+
replace_controller
|
50
|
+
when Integer
|
51
|
+
pid = msg
|
52
|
+
if pid == @controller_pid
|
53
|
+
puts 'Detected dead controller. Restarting...'
|
54
|
+
exit!
|
55
|
+
@controller_watcher.restart
|
56
|
+
end
|
57
|
+
else
|
58
|
+
raise "Invalid message received: #{msg.inspect}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def replace_controller
|
64
|
+
puts "Replacing controller"
|
65
|
+
old_watcher = @controller_watcher
|
66
|
+
@controller_watcher = start_controller_watcher
|
67
|
+
|
68
|
+
# TODO: we'll want to get some kind of signal from the new controller once it's ready
|
69
|
+
sleep 1
|
70
|
+
|
71
|
+
old_watcher.terminate(true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/tipi/version.rb
CHANGED
data/lib/tipi/websocket.rb
CHANGED
@@ -20,12 +20,12 @@ module Tipi
|
|
20
20
|
@version = headers['sec-websocket-version'].to_i
|
21
21
|
@reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
def recv
|
25
25
|
if (msg = @reader.next)
|
26
26
|
return msg.to_s
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
@conn.recv_loop do |data|
|
30
30
|
@reader << data
|
31
31
|
if (msg = @reader.next)
|
@@ -48,7 +48,7 @@ module Tipi
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
OutgoingFrame = ::WebSocket::Frame::Outgoing::Server
|
53
53
|
|
54
54
|
def send(data)
|
data/lib/tipi.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'polyphony'
|
4
|
+
|
4
5
|
require_relative './tipi/http1_adapter'
|
5
|
-
# require_relative './tipi/http1_adapter_new'
|
6
6
|
require_relative './tipi/http2_adapter'
|
7
7
|
require_relative './tipi/configuration'
|
8
8
|
require_relative './tipi/response_extensions'
|
9
|
+
require_relative './tipi/acme'
|
10
|
+
|
9
11
|
require 'qeweney/request'
|
10
12
|
|
11
13
|
class Qeweney::Request
|
@@ -15,7 +17,7 @@ end
|
|
15
17
|
module Tipi
|
16
18
|
ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
|
17
19
|
H2_PROTOCOL = 'h2'
|
18
|
-
|
20
|
+
|
19
21
|
class << self
|
20
22
|
def serve(host, port, opts = {}, &handler)
|
21
23
|
opts[:alpn_protocols] = ALPN_PROTOCOLS
|
@@ -24,7 +26,7 @@ module Tipi
|
|
24
26
|
ensure
|
25
27
|
server&.close
|
26
28
|
end
|
27
|
-
|
29
|
+
|
28
30
|
def listen(host, port, opts = {})
|
29
31
|
opts[:alpn_protocols] = ALPN_PROTOCOLS
|
30
32
|
Polyphony::Net.tcp_listen(host, port, opts).tap do |socket|
|
@@ -33,7 +35,7 @@ module Tipi
|
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
36
|
-
|
38
|
+
|
37
39
|
def accept_loop(server, opts, &handler)
|
38
40
|
server.accept_loop do |client|
|
39
41
|
spin { client_loop(client, opts, &handler) }
|
@@ -41,7 +43,7 @@ module Tipi
|
|
41
43
|
# disregard
|
42
44
|
end
|
43
45
|
end
|
44
|
-
|
46
|
+
|
45
47
|
def client_loop(client, opts, &handler)
|
46
48
|
client.no_delay if client.respond_to?(:no_delay)
|
47
49
|
adapter = protocol_adapter(client, opts)
|
@@ -49,11 +51,11 @@ module Tipi
|
|
49
51
|
ensure
|
50
52
|
client.close rescue nil
|
51
53
|
end
|
52
|
-
|
54
|
+
|
53
55
|
def protocol_adapter(socket, opts)
|
54
56
|
use_http2 = socket.respond_to?(:alpn_protocol) &&
|
55
57
|
socket.alpn_protocol == H2_PROTOCOL
|
56
|
-
klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
|
58
|
+
klass = use_http2 ? HTTP2Adapter : HTTP1Adapter
|
57
59
|
klass.new(socket, opts)
|
58
60
|
end
|
59
61
|
|
data/test/coverage.rb
CHANGED
@@ -26,10 +26,10 @@ module Coverage
|
|
26
26
|
@result = {}
|
27
27
|
trace = TracePoint.new(:line) do |tp|
|
28
28
|
next if tp.path =~ /\(/
|
29
|
-
|
29
|
+
|
30
30
|
absolute = File.expand_path(tp.path)
|
31
31
|
next unless LIB_FILES.include?(absolute)# =~ /^#{LIB_DIR}/
|
32
|
-
|
32
|
+
|
33
33
|
@result[absolute] ||= relevant_lines_for_filename(absolute)
|
34
34
|
@result[absolute][tp.lineno - 1] = 1
|
35
35
|
end
|
data/test/helper.rb
CHANGED
@@ -8,32 +8,29 @@ require_relative './eg'
|
|
8
8
|
require_relative './coverage' if ENV['COVERAGE']
|
9
9
|
|
10
10
|
require 'minitest/autorun'
|
11
|
-
require 'minitest/reporters'
|
12
11
|
|
13
12
|
require 'polyphony'
|
14
13
|
|
15
14
|
::Exception.__disable_sanitized_backtrace__ = true
|
16
15
|
|
17
|
-
Minitest::Reporters.use! [
|
18
|
-
Minitest::Reporters::SpecReporter.new
|
19
|
-
]
|
20
|
-
|
21
16
|
class MiniTest::Test
|
22
17
|
def setup
|
23
|
-
#
|
24
|
-
if Fiber.current.children.size > 0
|
25
|
-
puts "Children left: #{Fiber.current.children.inspect}"
|
26
|
-
exit!
|
27
|
-
end
|
18
|
+
# trace "* setup #{self.name}"
|
28
19
|
Fiber.current.setup_main_fiber
|
29
20
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
21
|
+
Thread.current.backend.finalize
|
30
22
|
Thread.current.backend = Polyphony::Backend.new
|
31
23
|
sleep 0
|
32
24
|
end
|
33
25
|
|
34
26
|
def teardown
|
35
|
-
#
|
27
|
+
# trace "* teardown #{self.name}"
|
36
28
|
Fiber.current.shutdown_all_children
|
29
|
+
if Fiber.current.children.size > 0
|
30
|
+
puts "Children left after #{self.name}: #{Fiber.current.children.inspect}"
|
31
|
+
exit!
|
32
|
+
end
|
33
|
+
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
37
34
|
rescue => e
|
38
35
|
puts e
|
39
36
|
puts e.backtrace.join("\n")
|
@@ -47,4 +44,55 @@ module Kernel
|
|
47
44
|
rescue Exception => e
|
48
45
|
e
|
49
46
|
end
|
50
|
-
|
47
|
+
|
48
|
+
def trace(*args)
|
49
|
+
STDOUT.orig_write(format_trace(args))
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_trace(args)
|
53
|
+
if args.first.is_a?(String)
|
54
|
+
if args.size > 1
|
55
|
+
format("%s: %p\n", args.shift, args)
|
56
|
+
else
|
57
|
+
format("%s\n", args.first)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
format("%p\n", args.size == 1 ? args.first : args)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class IO
|
66
|
+
# Creates two mockup sockets for simulating server-client communication
|
67
|
+
def self.server_client_mockup
|
68
|
+
server_in, client_out = IO.pipe
|
69
|
+
client_in, server_out = IO.pipe
|
70
|
+
|
71
|
+
server_connection = mockup_connection(server_in, server_out, client_out)
|
72
|
+
client_connection = mockup_connection(client_in, client_out, server_out)
|
73
|
+
|
74
|
+
[server_connection, client_connection]
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.mockup_connection(input, output, output2)
|
78
|
+
eg(
|
79
|
+
__parser_read_method__: ->() { :readpartial },
|
80
|
+
read: ->(*args) { input.read(*args) },
|
81
|
+
read_loop: ->(*args, &block) { input.read_loop(*args, &block) },
|
82
|
+
recv_loop: ->(*args, &block) { input.read_loop(*args, &block) },
|
83
|
+
readpartial: ->(*args) { input.readpartial(*args) },
|
84
|
+
recv: ->(*args) { input.readpartial(*args) },
|
85
|
+
'<<': ->(*args) { output.write(*args) },
|
86
|
+
write: ->(*args) { output.write(*args) },
|
87
|
+
close: -> { output.close },
|
88
|
+
eof?: -> { output2.closed? }
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
module Minitest::Assertions
|
94
|
+
def assert_in_range exp_range, act
|
95
|
+
msg = message(msg) { "Expected #{mu_pp(act)} to be in range #{mu_pp(exp_range)}" }
|
96
|
+
assert exp_range.include?(act), msg
|
97
|
+
end
|
98
|
+
end
|