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
|
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 108 bytes including \0 => 107)
# Note that Linux accepts not null-terminated paths but the man page advises against it.
if path.bytesize > 107
path = "/tmp/unix_server_spec.socket"
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)
require 'timeout'
time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
loop do
if Process.clock_gettime(Process::CLOCK_MONOTONIC) - time >= timeout
raise TimeoutError, "Did not succeed within #{timeout} seconds"
end
sleep 0.01 # necessary on OSX; don't know why
yield
end
end
def self.wait_until_success(timeout = TIME_TOLERANCE)
loop_with_timeout(timeout) do
begin
return yield
rescue
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.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
end
|