Skip to content

Commit ff9aff8

Browse files
committed
make TestHTTPServer choose the port automatically rather than guessing
1 parent 2c5d0af commit ff9aff8

File tree

1 file changed

+59
-23
lines changed

1 file changed

+59
-23
lines changed

TestFoundation/HTTPServer.swift

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ struct _HTTPUtils {
3535
static let EMPTY = ""
3636
}
3737

38+
extension UInt16 {
39+
public init(networkByteOrder input: UInt16) {
40+
self.init(bigEndian: input)
41+
}
42+
}
43+
3844
class _TCPSocket {
3945

4046
private var listenSocket: Int32!
@@ -55,12 +61,15 @@ class _TCPSocket {
5561
return r
5662
}
5763

58-
init(port: UInt16) throws {
64+
public private(set) var port: UInt16
65+
66+
init(port: UInt16?) throws {
5967
#if os(Linux)
6068
let SOCKSTREAM = Int32(SOCK_STREAM.rawValue)
6169
#else
6270
let SOCKSTREAM = SOCK_STREAM
6371
#endif
72+
self.port = port ?? 0
6473
listenSocket = try attempt("socket", valid: isNotNegative, socket(AF_INET, SOCKSTREAM, Int32(IPPROTO_TCP)))
6574
var on: Int = 1
6675
_ = try attempt("setsockopt", valid: isZero, setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &on, socklen_t(MemoryLayout<Int>.size)))
@@ -70,16 +79,26 @@ class _TCPSocket {
7079
let addr = UnsafePointer<sockaddr>($0)
7180
_ = try attempt("bind", valid: isZero, bind(listenSocket, addr, socklen_t(MemoryLayout<sockaddr>.size)))
7281
})
82+
83+
var actualSA = sockaddr_in()
84+
withUnsafeMutablePointer(to: &actualSA) { ptr in
85+
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { (ptr: UnsafeMutablePointer<sockaddr>) in
86+
var len = socklen_t(MemoryLayout<sockaddr>.size)
87+
getsockname(listenSocket, ptr, &len)
88+
}
89+
}
90+
91+
self.port = UInt16(networkByteOrder: actualSA.sin_port)
7392
}
7493

75-
private func createSockaddr(_ port: UInt16) -> sockaddr_in {
94+
private func createSockaddr(_ port: UInt16?) -> sockaddr_in {
7695
// Listen on the loopback address so that OSX doesnt pop up a dialog
7796
// asking to accept incoming connections if the firewall is enabled.
7897
let addr = UInt32(INADDR_LOOPBACK).bigEndian
7998
#if os(Linux)
80-
return sockaddr_in(sin_family: sa_family_t(AF_INET), sin_port: htons(port), sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
99+
return sockaddr_in(sin_family: sa_family_t(AF_INET), sin_port: htons(port ?? 0), sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
81100
#else
82-
return sockaddr_in(sin_len: 0, sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16HostToBig(port), sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
101+
return sockaddr_in(sin_len: 0, sin_family: sa_family_t(AF_INET), sin_port: CFSwapInt16HostToBig(port ?? 0), sin_addr: in_addr(s_addr: addr), sin_zero: (0,0,0,0,0,0,0,0))
83102
#endif
84103
}
85104

@@ -133,20 +152,27 @@ class _TCPSocket {
133152
}
134153

135154
func shutdown() {
136-
close(connectionSocket)
155+
if let connectionSocket = self.connectionSocket {
156+
close(connectionSocket)
157+
}
137158
close(listenSocket)
138159
}
139160
}
140161

141162
class _HTTPServer {
142163

143-
let socket: _TCPSocket
164+
let socket: _TCPSocket
165+
var port: UInt16 {
166+
get {
167+
return self.socket.port
168+
}
169+
}
144170

145-
init(port: UInt16) throws {
171+
init(port: UInt16?) throws {
146172
socket = try _TCPSocket(port: port)
147173
}
148174

149-
public class func create(port: UInt16) throws -> _HTTPServer {
175+
public class func create(port: UInt16?) throws -> _HTTPServer {
150176
return try _HTTPServer(port: port)
151177
}
152178

@@ -159,7 +185,7 @@ class _HTTPServer {
159185
}
160186

161187
public func request() throws -> _HTTPRequest {
162-
return _HTTPRequest(request: try socket.readData())
188+
return try _HTTPRequest(request: socket.readData())
163189
}
164190

165191
public func respond(with response: _HTTPResponse, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
@@ -307,8 +333,13 @@ public class TestURLSessionServer {
307333
let startDelay: TimeInterval?
308334
let sendDelay: TimeInterval?
309335
let bodyChunks: Int?
336+
var port: UInt16 {
337+
get {
338+
return self.httpServer.port
339+
}
340+
}
310341

311-
public init (port: UInt16, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
342+
public init (port: UInt16?, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
312343
httpServer = try _HTTPServer.create(port: port)
313344
self.startDelay = startDelay
314345
self.sendDelay = sendDelay
@@ -404,23 +435,28 @@ public class ServerSemaphore {
404435
}
405436

406437
class LoopbackServerTest : XCTestCase {
407-
static var serverPort: Int = -1
438+
private static let staticSyncQ = DispatchQueue(label: "org.swift.TestFoundation.HTTPServer.StaticSyncQ")
439+
440+
private static var _serverPort: Int = -1
441+
static var serverPort: Int {
442+
get {
443+
return staticSyncQ.sync { _serverPort }
444+
}
445+
set {
446+
staticSyncQ.sync { _serverPort = newValue }
447+
}
448+
}
408449

409450
override class func setUp() {
410451
super.setUp()
411452
func runServer(with condition: ServerSemaphore, startDelay: TimeInterval? = nil, sendDelay: TimeInterval? = nil, bodyChunks: Int? = nil) throws {
412-
let start = 21961
413-
for port in start...(start+100) { //we must find at least one port to bind
414-
do {
415-
serverPort = port
416-
let test = try TestURLSessionServer(port: UInt16(port), startDelay: startDelay, sendDelay: sendDelay, bodyChunks: bodyChunks)
417-
try test.start(started: condition)
418-
try test.readAndRespond()
419-
test.stop()
420-
} catch let e as ServerError {
421-
if e.operation == "bind" { continue }
422-
throw e
423-
}
453+
while true {
454+
let test = try TestURLSessionServer(port: nil, startDelay: startDelay, sendDelay: sendDelay, bodyChunks: bodyChunks)
455+
serverPort = Int(test.port)
456+
try test.start(started: condition)
457+
try test.readAndRespond()
458+
serverPort = -2
459+
test.stop()
424460
}
425461
}
426462

0 commit comments

Comments
 (0)