Skip to content

Commit 8e4ddae

Browse files
authored
Make completion-based send/receive functions public in URLSessionWebSocketTask (#5030)
1 parent 1ed5181 commit 8e4ddae

File tree

2 files changed

+105
-2
lines changed

2 files changed

+105
-2
lines changed

Diff for: Sources/FoundationNetworking/URLSession/URLSessionTask.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,8 @@ open class URLSessionWebSocketTask : URLSessionTask, @unchecked Sendable {
830830
}
831831
}
832832

833-
private func send(_ message: Message, completionHandler: @Sendable @escaping (Error?) -> Void) {
833+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
834+
public func send(_ message: Message, completionHandler: @Sendable @escaping (Error?) -> Void) {
834835
self.workQueue.async {
835836
self.sendBuffer.append((message, completionHandler))
836837
self.doPendingWork()
@@ -846,7 +847,8 @@ open class URLSessionWebSocketTask : URLSessionTask, @unchecked Sendable {
846847
}
847848
}
848849

849-
private func receive(completionHandler: @Sendable @escaping (Result<Message, Error>) -> Void) {
850+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
851+
public func receive(completionHandler: @Sendable @escaping (Result<Message, Error>) -> Void) {
850852
self.workQueue.async {
851853
self.receiveCompletionHandlers.append(completionHandler)
852854
self.doPendingWork()

Diff for: Tests/Foundation/TestURLSession.swift

+101
Original file line numberDiff line numberDiff line change
@@ -2146,6 +2146,107 @@ final class TestURLSession: LoopbackServerTest, @unchecked Sendable {
21462146
XCTAssertEqual(delegate.callbacks, callbacks, "Callbacks for \(#function)")
21472147
}
21482148

2149+
func test_webSocketCompletions() async throws {
2150+
guard #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
2151+
guard URLSessionWebSocketTask.supportsWebSockets else {
2152+
print("libcurl lacks WebSockets support, skipping \(#function)")
2153+
return
2154+
}
2155+
2156+
let urlString = "ws://127.0.0.1:\(TestURLSession.serverPort)/web-socket"
2157+
let url = try XCTUnwrap(URL(string: urlString))
2158+
let request = URLRequest(url: url)
2159+
2160+
let delegate = SessionDelegate(with: expectation(description: "\(urlString): Connect"))
2161+
let task = delegate.runWebSocketTask(with: request, timeoutInterval: 4)
2162+
2163+
// We interleave sending and receiving, as the test HTTPServer implementation is barebones, and can't handle receiving more than one frame at a time. So, this back-and-forth acts as a gating mechanism
2164+
2165+
let didCompleteSendingString = expectation(description: "Did complete sending a string")
2166+
task.send(.string("Hello")) { error in
2167+
XCTAssertNil(error)
2168+
didCompleteSendingString.fulfill()
2169+
}
2170+
await fulfillment(of: [didCompleteSendingString], timeout: 5.0)
2171+
2172+
let didCompleteReceivingString = expectation(description: "Did complete receiving a string")
2173+
task.receive { result in
2174+
switch result {
2175+
case .failure(let error):
2176+
XCTFail()
2177+
case .success(let stringMessage):
2178+
switch stringMessage {
2179+
case .string(let str):
2180+
XCTAssert(str == "Hello")
2181+
default:
2182+
XCTFail("Unexpected String Message")
2183+
}
2184+
}
2185+
didCompleteReceivingString.fulfill()
2186+
}
2187+
await fulfillment(of: [didCompleteReceivingString], timeout: 5.0)
2188+
2189+
let didCompleteSendingData = expectation(description: "Did complete sending data")
2190+
task.send(.data(Data([0x20, 0x22, 0x10, 0x03]))) { error in
2191+
XCTAssertNil(error)
2192+
didCompleteSendingData.fulfill()
2193+
}
2194+
await fulfillment(of: [didCompleteSendingData], timeout: 5.0)
2195+
2196+
let didCompleteReceivingData = expectation(description: "Did complete receiving data")
2197+
task.receive { result in
2198+
switch result {
2199+
case .failure(let error):
2200+
XCTFail()
2201+
case .success(let dataMessage):
2202+
switch dataMessage {
2203+
case .data(let data):
2204+
XCTAssert(data == Data([0x20, 0x22, 0x10, 0x03]))
2205+
default:
2206+
XCTFail("Unexpected Data Message")
2207+
}
2208+
}
2209+
didCompleteReceivingData.fulfill()
2210+
}
2211+
await fulfillment(of: [didCompleteReceivingData], timeout: 5.0)
2212+
2213+
let didCompleteSendingPing = expectation(description: "Did complete sending ping")
2214+
task.sendPing { error in
2215+
if let error {
2216+
// Server closed the connection before we could process the pong
2217+
if let urlError = error as? URLError {
2218+
XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost)
2219+
} else {
2220+
XCTFail("Unexpecter error type")
2221+
}
2222+
}
2223+
didCompleteSendingPing.fulfill()
2224+
}
2225+
await fulfillment(of: [delegate.expectation, didCompleteSendingPing], timeout: 50.0)
2226+
2227+
let didCompleteReceiving = expectation(description: "Did complete receiving")
2228+
task.receive { result in
2229+
switch result {
2230+
case .failure(let error):
2231+
if let urlError = error as? URLError {
2232+
XCTAssertEqual(urlError._nsError.code, NSURLErrorNetworkConnectionLost)
2233+
} else {
2234+
XCTFail("Unexpecter error type")
2235+
}
2236+
case .success:
2237+
XCTFail("Expected to throw when receiving on closed task")
2238+
}
2239+
didCompleteReceiving.fulfill()
2240+
}
2241+
await fulfillment(of: [didCompleteReceiving], timeout: 5.0)
2242+
2243+
let callbacks = [ "urlSession(_:webSocketTask:didOpenWithProtocol:)",
2244+
"urlSession(_:webSocketTask:didCloseWith:reason:)",
2245+
"urlSession(_:task:didCompleteWithError:)" ]
2246+
XCTAssertEqual(delegate.callbacks.count, callbacks.count)
2247+
XCTAssertEqual(delegate.callbacks, callbacks, "Callbacks for \(#function)")
2248+
}
2249+
21492250
func test_webSocketSpecificProtocol() async throws {
21502251
guard #available(macOS 12, iOS 13.0, watchOS 6.0, tvOS 13.0, *) else { return }
21512252
guard URLSessionWebSocketTask.supportsWebSockets else {

0 commit comments

Comments
 (0)