tipi 0.41 → 0.46
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.
- 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
|