Skip to content

Commit d25d0c5

Browse files
committed
Add tests to ensure that server-side timeouts are handled as expected.
1 parent c1b54e6 commit d25d0c5

File tree

4 files changed

+162
-3
lines changed

4 files changed

+162
-3
lines changed

Tests/LinuxMain.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,7 @@ import XCTest
1818

1919
XCTMain([
2020
testCase(gRPCTests.allTests),
21-
testCase(EchoTests.allTests)
21+
testCase(EchoTests.allTests),
22+
testCase(ErrorHandlingTests.allTests),
23+
testCase(ServerTimeoutTests.allTests)
2224
])

Tests/SwiftGRPCTests/EchoTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class EchoTests: XCTestCase {
4141

4242
static let lotsOfStrings = (0..<1000).map { String(describing: $0) }
4343

44-
let defaultTimeout: TimeInterval = 5.0
44+
let defaultTimeout: TimeInterval = 1.0
4545

4646
let provider = EchoProvider()
4747
var server: Echo_EchoServer!

Tests/SwiftGRPCTests/ErrorHandlingTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ErrorHandlingTests: XCTestCase {
3131

3232
let address = "localhost:5050"
3333

34-
let defaultTimeout: TimeInterval = 1.0
34+
let defaultTimeout: TimeInterval = 0.5
3535
}
3636

3737
extension ErrorHandlingTests {
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*
2+
* Copyright 2018, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import Dispatch
17+
import Foundation
18+
@testable import SwiftGRPC
19+
import XCTest
20+
21+
fileprivate class TimingOutEchoProvider: Echo_EchoProvider {
22+
func get(request: Echo_EchoRequest, session _: Echo_EchoGetSession) throws -> Echo_EchoResponse {
23+
Thread.sleep(forTimeInterval: 0.2)
24+
return Echo_EchoResponse()
25+
}
26+
27+
func expand(request: Echo_EchoRequest, session: Echo_EchoExpandSession) throws {
28+
Thread.sleep(forTimeInterval: 0.2)
29+
}
30+
31+
func collect(session: Echo_EchoCollectSession) throws {
32+
Thread.sleep(forTimeInterval: 0.2)
33+
}
34+
35+
func update(session: Echo_EchoUpdateSession) throws {
36+
Thread.sleep(forTimeInterval: 0.2)
37+
}
38+
}
39+
40+
class ServerTimeoutTests: XCTestCase {
41+
static var allTests: [(String, (ServerTimeoutTests) -> () throws -> Void)] {
42+
return [
43+
("testTimeoutUnary", testTimeoutUnary),
44+
("testTimeoutClientStreaming", testTimeoutClientStreaming),
45+
("testTimeoutServerStreaming", testTimeoutServerStreaming),
46+
("testTimeoutBidirectionalStreaming", testTimeoutBidirectionalStreaming)
47+
]
48+
}
49+
50+
let defaultTimeout: TimeInterval = 0.1
51+
52+
fileprivate let provider = TimingOutEchoProvider()
53+
var server: Echo_EchoServer!
54+
var client: Echo_EchoServiceClient!
55+
56+
override func setUp() {
57+
super.setUp()
58+
59+
let address = "localhost:5050"
60+
server = Echo_EchoServer(address: address, provider: provider)
61+
server.start(queue: DispatchQueue.global())
62+
client = Echo_EchoServiceClient(address: address, secure: false)
63+
64+
client.timeout = defaultTimeout
65+
}
66+
67+
override func tearDown() {
68+
client = nil
69+
70+
server.server.stop()
71+
server = nil
72+
73+
super.tearDown()
74+
}
75+
}
76+
77+
extension ServerTimeoutTests {
78+
func testTimeoutUnary() {
79+
do {
80+
_ = try client.get(Echo_EchoRequest(text: "foo")).text
81+
XCTFail("should have thrown")
82+
} catch {
83+
guard case let .callError(callResult) = error as! RPCError
84+
else { XCTFail("unexpected error \(error)"); return }
85+
XCTAssertEqual(.deadlineExceeded, callResult.statusCode)
86+
XCTAssertEqual("Deadline Exceeded", callResult.statusMessage)
87+
}
88+
}
89+
90+
func testTimeoutClientStreaming() {
91+
let completionHandlerExpectation = expectation(description: "final completion handler called")
92+
let call = try! client.collect { callResult in
93+
XCTAssertEqual(.deadlineExceeded, callResult.statusCode)
94+
completionHandlerExpectation.fulfill()
95+
}
96+
97+
let sendExpectation = expectation(description: "send completion handler 1 called")
98+
try! call.send(Echo_EchoRequest(text: "foo")) { [sendExpectation] in
99+
// The server only times out later in its lifecycle, so we shouldn't get an error when trying to send a message.
100+
XCTAssertNil($0)
101+
sendExpectation.fulfill()
102+
}
103+
call.waitForSendOperationsToFinish()
104+
105+
do {
106+
_ = try call.closeAndReceive()
107+
XCTFail("should have thrown")
108+
} catch let receiveError {
109+
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
110+
}
111+
112+
waitForExpectations(timeout: defaultTimeout)
113+
}
114+
115+
func testTimeoutServerStreaming() {
116+
let completionHandlerExpectation = expectation(description: "completion handler called")
117+
let call = try! client.expand(Echo_EchoRequest(text: "foo bar baz")) { callResult in
118+
XCTAssertEqual(.deadlineExceeded, callResult.statusCode)
119+
completionHandlerExpectation.fulfill()
120+
}
121+
122+
// TODO(danielalm): Why doesn't `call.receive()` throw once the call times out?
123+
XCTAssertNil(try! call.receive())
124+
125+
waitForExpectations(timeout: defaultTimeout)
126+
}
127+
128+
func testTimeoutBidirectionalStreaming() {
129+
let completionHandlerExpectation = expectation(description: "completion handler called")
130+
let call = try! client.update { callResult in
131+
XCTAssertEqual(.deadlineExceeded, callResult.statusCode)
132+
completionHandlerExpectation.fulfill()
133+
}
134+
135+
let sendExpectation = expectation(description: "send completion handler 1 called")
136+
try! call.send(Echo_EchoRequest(text: "foo")) { [sendExpectation] in
137+
// The server only times out later in its lifecycle, so we shouldn't get an error when trying to send a message.
138+
XCTAssertNil($0)
139+
sendExpectation.fulfill()
140+
}
141+
call.waitForSendOperationsToFinish()
142+
143+
// FIXME(danielalm): Why does `call.receive()` only throw on Linux once the call times out?
144+
#if os(Linux)
145+
do {
146+
_ = try call.receive()
147+
XCTFail("should have thrown")
148+
} catch let receiveError {
149+
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
150+
}
151+
#else
152+
XCTAssertNil(try! call.receive())
153+
#endif
154+
155+
waitForExpectations(timeout: defaultTimeout)
156+
}
157+
}

0 commit comments

Comments
 (0)