Skip to content

Commit 7516d8d

Browse files
committed
Add tests to verify the behavior of canceling client and server calls.
Also changes a few method signatures where passing a `CallResult` does not make sense.
1 parent 8d07183 commit 7516d8d

18 files changed

+312
-48
lines changed

Sources/Examples/Echo/Generated/echo.grpc.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ internal protocol Echo_EchoExpandSession: ServerSessionServerStreaming {
212212

213213
/// Close the connection and send the status. Non-blocking.
214214
/// You MUST call this method once you are done processing the request.
215-
func close(withStatus status: ServerStatus, completion: ((CallResult) -> Void)?) throws
215+
func close(withStatus status: ServerStatus, completion: (() -> Void)?) throws
216216
}
217217

218218
fileprivate final class Echo_EchoExpandSessionBase: ServerSessionServerStreamingBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoExpandSession {}
@@ -227,11 +227,11 @@ internal protocol Echo_EchoCollectSession: ServerSessionClientStreaming {
227227

228228
/// You MUST call one of these two methods once you are done processing the request.
229229
/// Close the connection and send a single result. Non-blocking.
230-
func sendAndClose(response: Echo_EchoResponse, status: ServerStatus, completion: ((CallResult) -> Void)?) throws
230+
func sendAndClose(response: Echo_EchoResponse, status: ServerStatus, completion: (() -> Void)?) throws
231231
/// Close the connection and send an error. Non-blocking.
232232
/// Use this method if you encountered an error that makes it impossible to send a response.
233233
/// Accordingly, it does not make sense to call this method with a status of `.ok`.
234-
func sendErrorAndClose(status: ServerStatus, completion: ((CallResult) -> Void)?) throws
234+
func sendErrorAndClose(status: ServerStatus, completion: (() -> Void)?) throws
235235
}
236236

237237
fileprivate final class Echo_EchoCollectSessionBase: ServerSessionClientStreamingBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoCollectSession {}
@@ -251,7 +251,7 @@ internal protocol Echo_EchoUpdateSession: ServerSessionBidirectionalStreaming {
251251

252252
/// Close the connection and send the status. Non-blocking.
253253
/// You MUST call this method once you are done processing the request.
254-
func close(withStatus status: ServerStatus, completion: ((CallResult) -> Void)?) throws
254+
func close(withStatus status: ServerStatus, completion: (() -> Void)?) throws
255255
}
256256

257257
fileprivate final class Echo_EchoUpdateSessionBase: ServerSessionBidirectionalStreamingBase<Echo_EchoRequest, Echo_EchoResponse>, Echo_EchoUpdateSession {}

Sources/SwiftGRPC/Core/Call.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ public class Call {
9999
/// - Parameter metadata: metadata to send with the call
100100
/// - Parameter message: data containing the message to send (.unary and .serverStreaming only)
101101
/// - Parameter completion: a block to call with call results
102+
/// The argument to `completion` will always have `.success = true`
103+
/// because operations containing `.receiveCloseOnClient` always succeed.
102104
/// - Throws: `CallError` if fails to call.
103105
public func start(_ style: CallStyle,
104106
metadata: Metadata,

Sources/SwiftGRPC/Core/Handler.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,31 +121,33 @@ public class Handler {
121121
})
122122
}
123123

124-
/// Sends the response to a request
124+
/// Sends the response to a request.
125+
/// The completion handler does not take an argument because operations containing `.receiveCloseOnServer` always succeed.
125126
public func sendResponse(message: Data, status: ServerStatus,
126-
completion: ((CallResult) -> Void)? = nil) throws {
127+
completion: (() -> Void)? = nil) throws {
127128
let messageBuffer = ByteBuffer(data: message)
128129
try call.perform(OperationGroup(
129130
call: call,
130131
operations: [
131132
.sendMessage(messageBuffer),
132133
.receiveCloseOnServer,
133134
.sendStatusFromServer(status.code, status.message, status.trailingMetadata)
134-
]) { operationGroup in
135-
completion?(CallResult(operationGroup))
135+
]) { _ in
136+
completion?()
136137
self.shutdown()
137138
})
138139
}
139140

140-
/// Send final status to the client
141-
public func sendStatus(_ status: ServerStatus, completion: ((CallResult) -> Void)? = nil) throws {
141+
/// Send final status to the client.
142+
/// The completion handler does not take an argument because operations containing `.receiveCloseOnServer` always succeed.
143+
public func sendStatus(_ status: ServerStatus, completion: (() -> Void)? = nil) throws {
142144
try call.perform(OperationGroup(
143145
call: call,
144146
operations: [
145147
.receiveCloseOnServer,
146148
.sendStatusFromServer(status.code, status.message, status.trailingMetadata)
147-
]) { operationGroup in
148-
completion?(CallResult(operationGroup))
149+
]) { _ in
150+
completion?()
149151
self.shutdown()
150152
})
151153
}

Sources/SwiftGRPC/Runtime/ClientCallUnary.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ open class ClientCallUnaryBase<InputType: Message, OutputType: Message>: ClientC
4747
completion: @escaping ((OutputType?, CallResult) -> Void)) throws -> Self {
4848
let requestData = try request.serializedData()
4949
try call.start(.unary, metadata: metadata, message: requestData) { callResult in
50-
if let responseData = callResult.resultData,
51-
let response = try? OutputType(serializedData: responseData) {
52-
completion(response, callResult)
50+
if let responseData = callResult.resultData {
51+
completion(try? OutputType(serializedData: responseData), callResult)
5352
} else {
5453
completion(nil, callResult)
5554
}

Sources/SwiftGRPC/Runtime/ServerSession.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public protocol ServerSession: class {
3939
var requestMetadata: Metadata { get }
4040

4141
var initialMetadata: Metadata { get set }
42+
43+
func cancel()
4244
}
4345

4446
open class ServerSessionBase: ServerSession {
@@ -52,6 +54,10 @@ open class ServerSessionBase: ServerSession {
5254
public init(handler: Handler) {
5355
self.handler = handler
5456
}
57+
58+
public func cancel() {
59+
call.cancel()
60+
}
5561
}
5662

5763
open class ServerSessionTestStub: ServerSession {
@@ -60,4 +66,6 @@ open class ServerSessionTestStub: ServerSession {
6066
open var initialMetadata = Metadata()
6167

6268
public init() {}
69+
70+
open func cancel() {}
6371
}

Sources/SwiftGRPC/Runtime/ServerSessionBidirectionalStreaming.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ open class ServerSessionBidirectionalStreamingTestStub<InputType: Message, Outpu
8686
outputs.append(message)
8787
}
8888

89-
open func close(withStatus status: ServerStatus, completion: ((CallResult) -> Void)?) throws {
89+
open func close(withStatus status: ServerStatus, completion: (() -> Void)?) throws {
9090
self.status = status
91-
completion?(.fakeOK)
91+
completion?()
9292
}
9393

9494
open func waitForSendOperationsToFinish() {}

Sources/SwiftGRPC/Runtime/ServerSessionClientStreaming.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ open class ServerSessionClientStreamingBase<InputType: Message, OutputType: Mess
3232
}
3333

3434
public func sendAndClose(response: OutputType, status: ServerStatus = .ok,
35-
completion: ((CallResult) -> Void)? = nil) throws {
35+
completion: (() -> Void)? = nil) throws {
3636
try handler.sendResponse(message: response.serializedData(), status: status, completion: completion)
3737
}
3838

39-
public func sendErrorAndClose(status: ServerStatus, completion: ((CallResult) -> Void)? = nil) throws {
39+
public func sendErrorAndClose(status: ServerStatus, completion: (() -> Void)? = nil) throws {
4040
try handler.sendStatus(status, completion: completion)
4141
}
4242

@@ -84,15 +84,15 @@ open class ServerSessionClientStreamingTestStub<InputType: Message, OutputType:
8484
completion(.result(try self.receive()))
8585
}
8686

87-
open func sendAndClose(response: OutputType, status: ServerStatus, completion: ((CallResult) -> Void)?) throws {
87+
open func sendAndClose(response: OutputType, status: ServerStatus, completion: (() -> Void)?) throws {
8888
self.output = response
8989
self.status = status
90-
completion?(.fakeOK)
90+
completion?()
9191
}
9292

93-
open func sendErrorAndClose(status: ServerStatus, completion: ((CallResult) -> Void)? = nil) throws {
93+
open func sendErrorAndClose(status: ServerStatus, completion: (() -> Void)? = nil) throws {
9494
self.status = status
95-
completion?(.fakeOK)
95+
completion?()
9696
}
9797

9898
open func close() throws {}

Sources/SwiftGRPC/Runtime/ServerSessionServerStreaming.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ open class ServerSessionServerStreamingTestStub<OutputType: Message>: ServerSess
7676
outputs.append(message)
7777
}
7878

79-
open func close(withStatus status: ServerStatus, completion: ((CallResult) -> Void)?) throws {
79+
open func close(withStatus status: ServerStatus, completion: (() -> Void)?) throws {
8080
self.status = status
81-
completion?(.fakeOK)
81+
completion?()
8282
}
8383

8484
open func waitForSendOperationsToFinish() {}

Sources/SwiftGRPC/Runtime/StreamSending.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension StreamSending {
4848
}
4949

5050
extension StreamSending where Self: ServerSessionBase {
51-
public func close(withStatus status: ServerStatus = .ok, completion: ((CallResult) -> Void)? = nil) throws {
51+
public func close(withStatus status: ServerStatus = .ok, completion: (() -> Void)? = nil) throws {
5252
try handler.sendStatus(status, completion: completion)
5353
}
5454
}

Sources/protoc-gen-swiftgrpc/Generator-Server.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,11 @@ extension Generator {
145145
private func printServerMethodSendAndClose(sentType: String) {
146146
println("/// You MUST call one of these two methods once you are done processing the request.")
147147
println("/// Close the connection and send a single result. Non-blocking.")
148-
println("func sendAndClose(response: \(sentType), status: ServerStatus, completion: ((CallResult) -> Void)?) throws")
148+
println("func sendAndClose(response: \(sentType), status: ServerStatus, completion: (() -> Void)?) throws")
149149
println("/// Close the connection and send an error. Non-blocking.")
150150
println("/// Use this method if you encountered an error that makes it impossible to send a response.")
151151
println("/// Accordingly, it does not make sense to call this method with a status of `.ok`.")
152-
println("func sendErrorAndClose(status: ServerStatus, completion: ((CallResult) -> Void)?) throws")
152+
println("func sendErrorAndClose(status: ServerStatus, completion: (() -> Void)?) throws")
153153
}
154154

155155
private func printServerMethodClientStreaming() {
@@ -171,7 +171,7 @@ extension Generator {
171171
private func printServerMethodClose() {
172172
println("/// Close the connection and send the status. Non-blocking.")
173173
println("/// You MUST call this method once you are done processing the request.")
174-
println("func close(withStatus status: ServerStatus, completion: ((CallResult) -> Void)?) throws")
174+
println("func close(withStatus status: ServerStatus, completion: (() -> Void)?) throws")
175175
}
176176

177177
private func printServerMethodServerStreaming() {

Tests/LinuxMain.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import XCTest
1818

1919
XCTMain([
2020
testCase(gRPCTests.allTests),
21+
testCase(ClientCancellingTests.allTests),
2122
testCase(ClientTestExample.allTests),
2223
testCase(ClientTimeoutTests.allTests),
2324
testCase(ConnectionFailureTests.allTests),
2425
testCase(EchoTests.allTests),
26+
testCase(ServerCancellingTests.allTests),
2527
testCase(ServerTestExample.allTests),
2628
testCase(ServerThrowingTests.allTests),
2729
testCase(ServerTimeoutTests.allTests)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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+
class ClientCancellingTests: BasicEchoTestCase {
22+
static var allTests: [(String, (ClientCancellingTests) -> () throws -> Void)] {
23+
return [
24+
("testUnary", testUnary),
25+
("testClientStreaming", testClientStreaming),
26+
("testServerStreaming", testServerStreaming),
27+
("testBidirectionalStreaming", testBidirectionalStreaming),
28+
]
29+
}
30+
31+
static let lotsOfStrings = (0..<1000).map { String(describing: $0) }
32+
}
33+
34+
extension ClientCancellingTests {
35+
func testUnary() {
36+
let completionHandlerExpectation = expectation(description: "final completion handler called")
37+
let call = try! client.get(Echo_EchoRequest(text: "foo bar baz")) { response, callResult in
38+
XCTAssertNil(response)
39+
XCTAssertEqual(.cancelled, callResult.statusCode)
40+
completionHandlerExpectation.fulfill()
41+
}
42+
43+
call.cancel()
44+
45+
waitForExpectations(timeout: defaultTimeout)
46+
}
47+
48+
func testClientStreaming() {
49+
let completionHandlerExpectation = expectation(description: "final completion handler called")
50+
let call = try! client.collect { callResult in
51+
XCTAssertEqual(.cancelled, callResult.statusCode)
52+
completionHandlerExpectation.fulfill()
53+
}
54+
55+
call.cancel()
56+
57+
let sendExpectation = expectation(description: "send completion handler 1 called")
58+
try! call.send(Echo_EchoRequest(text: "foo")) { [sendExpectation] in XCTAssertEqual(.unknown, $0 as! CallError); sendExpectation.fulfill() }
59+
call.waitForSendOperationsToFinish()
60+
61+
do {
62+
let result = try call.closeAndReceive()
63+
XCTFail("should have thrown, received \(result) instead")
64+
} catch let receiveError {
65+
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
66+
}
67+
68+
waitForExpectations(timeout: defaultTimeout)
69+
}
70+
71+
func testServerStreaming() {
72+
let completionHandlerExpectation = expectation(description: "completion handler called")
73+
let call = try! client.expand(Echo_EchoRequest(text: "foo bar baz")) { callResult in
74+
XCTAssertEqual(.cancelled, callResult.statusCode)
75+
completionHandlerExpectation.fulfill()
76+
}
77+
78+
XCTAssertEqual("Swift echo expand (0): foo", try! call.receive()!.text)
79+
80+
call.cancel()
81+
82+
do {
83+
let result = try call.receive()
84+
XCTFail("should have thrown, received \(String(describing: result)) instead")
85+
} catch let receiveError {
86+
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
87+
}
88+
89+
waitForExpectations(timeout: defaultTimeout)
90+
}
91+
92+
func testBidirectionalStreaming() {
93+
let finalCompletionHandlerExpectation = expectation(description: "final completion handler called")
94+
let call = try! client.update { callResult in
95+
XCTAssertEqual(.cancelled, callResult.statusCode)
96+
finalCompletionHandlerExpectation.fulfill()
97+
}
98+
99+
var sendExpectation = expectation(description: "send completion handler 1 called")
100+
try! call.send(Echo_EchoRequest(text: "foo")) { [sendExpectation] in XCTAssertNil($0); sendExpectation.fulfill() }
101+
XCTAssertEqual("Swift echo update (0): foo", try! call.receive()!.text)
102+
103+
call.cancel()
104+
105+
sendExpectation = expectation(description: "send completion handler 2 called")
106+
try! call.send(Echo_EchoRequest(text: "bar")) { [sendExpectation] in XCTAssertEqual(.unknown, $0 as! CallError); sendExpectation.fulfill() }
107+
do {
108+
let result = try call.receive()
109+
XCTFail("should have thrown, received \(String(describing: result)) instead")
110+
} catch let receiveError {
111+
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
112+
}
113+
114+
let closeCompletionHandlerExpectation = expectation(description: "close completion handler called")
115+
try! call.closeSend { closeCompletionHandlerExpectation.fulfill() }
116+
117+
waitForExpectations(timeout: defaultTimeout)
118+
}
119+
}

Tests/SwiftGRPCTests/ClientTimeoutTests.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ extension ClientTimeoutTests {
4747
call.waitForSendOperationsToFinish()
4848

4949
do {
50-
_ = try call.closeAndReceive()
51-
XCTFail("should have thrown")
50+
let result = try call.closeAndReceive()
51+
XCTFail("should have thrown, received \(result) instead")
5252
} catch let receiveError {
5353
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
5454
}
@@ -70,8 +70,8 @@ extension ClientTimeoutTests {
7070
Thread.sleep(forTimeInterval: 0.2)
7171

7272
do {
73-
_ = try call.closeAndReceive()
74-
XCTFail("should have thrown")
73+
let result = try call.closeAndReceive()
74+
XCTFail("should have thrown, received \(result) instead")
7575
} catch let receiveError {
7676
XCTAssertEqual(.unknown, (receiveError as! RPCError).callResult!.statusCode)
7777
}

0 commit comments

Comments
 (0)