-
-
Notifications
You must be signed in to change notification settings - Fork 389
/
Copy pathclasses.rb
166 lines (139 loc) · 4.02 KB
/
classes.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require 'socket'
module SocketSpecs
# helper to get the hostname associated to 127.0.0.1 or the given ip
def self.hostname(ip = "127.0.0.1")
# Calculate each time, without caching, since the result might
# depend on things like do_not_reverse_lookup mode, which is
# changing from test to test
Socket.getaddrinfo(ip, nil)[0][2]
end
def self.hostname_reverse_lookup(ip = "127.0.0.1")
Socket.getaddrinfo(ip, nil, 0, 0, 0, 0, true)[0][2]
end
def self.addr(which=:ipv4)
case which
when :ipv4
host = "127.0.0.1"
when :ipv6
host = "::1"
end
Socket.getaddrinfo(host, nil)[0][3]
end
def self.reserved_unused_port
# https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
0
end
def self.sockaddr_in(port, host)
Socket::SockAddr_In.new(Socket.sockaddr_in(port, host))
end
def self.socket_path
path = tmp("unix.sock", false)
# Check for too long unix socket path (max 104 bytes on macOS)
# Note that Linux accepts not null-terminated paths but the man page advises against it.
if path.bytesize > 104
# rm_r in spec/mspec/lib/mspec/helpers/fs.rb fails against
# "/tmp/unix_server_spec.socket"
skip "too long unix socket path: #{path}"
end
rm_socket(path)
path
end
def self.rm_socket(path)
File.delete(path) if File.exist?(path)
end
def self.ipv6_available?
@ipv6_available ||= begin
server = TCPServer.new('::1', 0)
rescue Errno::EAFNOSUPPORT, Errno::EADDRNOTAVAIL, SocketError
:no
else
server.close
:yes
end
@ipv6_available == :yes
end
def self.each_ip_protocol
describe 'using IPv4' do
yield Socket::AF_INET, '127.0.0.1', 'AF_INET'
end
guard -> { SocketSpecs.ipv6_available? } do
describe 'using IPv6' do
yield Socket::AF_INET6, '::1', 'AF_INET6'
end
end
end
def self.loop_with_timeout(timeout = TIME_TOLERANCE)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
while yield == :retry
if Process.clock_gettime(Process::CLOCK_MONOTONIC) - start >= timeout
raise RuntimeError, "Did not succeed within #{timeout} seconds"
end
end
end
def self.dest_addr_req_error
error = Errno::EDESTADDRREQ
platform_is :windows do
error = Errno::ENOTCONN
end
error
end
# TCPServer echo server accepting one connection
class SpecTCPServer
attr_reader :hostname, :port
def initialize
@hostname = SocketSpecs.hostname
@server = TCPServer.new @hostname, 0
@port = @server.addr[1]
log "SpecTCPServer starting on #{@hostname}:#{@port}"
@thread = Thread.new do
socket = @server.accept
log "SpecTCPServer accepted connection: #{socket}"
service socket
end
end
def service(socket)
begin
data = socket.recv(1024)
return if data.nil? || data.empty?
log "SpecTCPServer received: #{data.inspect}"
return if data == "QUIT"
socket.send data, 0
ensure
socket.close
end
end
def shutdown
log "SpecTCPServer shutting down"
@thread.join
@server.close
end
def log(message)
@logger.puts message if @logger
end
end
# We need to find a free port for Socket.tcp_server_loop and Socket.udp_server_loop,
# and the only reliable way to do that is to pass 0 as the port, but then we need to
# find out which one was chosen and the API doesn't let us find what it is. So we
# intercept one of the public API methods called by these methods.
class ServerLoopPortFinder < Socket
def self.tcp_server_sockets(*args)
super(*args) { |sockets|
@port = sockets.first.local_address.ip_port
yield(sockets)
}
end
def self.udp_server_sockets(*args, &block)
super(*args) { |sockets|
@port = sockets.first.local_address.ip_port
yield(sockets)
}
end
def self.cleanup
@port = nil
end
def self.port
sleep 0.001 until @port
@port
end
end
end