GRPCClientTests.m 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. /*
  2. *
  3. * Copyright 2015 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *
  17. */
  18. #import <UIKit/UIKit.h>
  19. #import <XCTest/XCTest.h>
  20. #import <grpc/grpc.h>
  21. #import <GRPCClient/GRPCCall+ChannelArg.h>
  22. #import <GRPCClient/GRPCCall+OAuth2.h>
  23. #import <GRPCClient/GRPCCall+Tests.h>
  24. #import <GRPCClient/GRPCCall.h>
  25. #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
  26. #import <ProtoRPC/ProtoMethod.h>
  27. #import <RemoteTest/Messages.pbobjc.h>
  28. #import <RxLibrary/GRXBufferedPipe.h>
  29. #import <RxLibrary/GRXWriteable.h>
  30. #import <RxLibrary/GRXWriter+Immediate.h>
  31. #include <netinet/in.h>
  32. #import "version.h"
  33. #define TEST_TIMEOUT 16
  34. static NSString *const kHostAddress = @"localhost:5050";
  35. static NSString *const kPackage = @"grpc.testing";
  36. static NSString *const kService = @"TestService";
  37. static NSString *const kRemoteSSLHost = @"grpc-test.sandbox.googleapis.com";
  38. static GRPCProtoMethod *kInexistentMethod;
  39. static GRPCProtoMethod *kEmptyCallMethod;
  40. static GRPCProtoMethod *kUnaryCallMethod;
  41. static GRPCProtoMethod *kFullDuplexCallMethod;
  42. /** Observer class for testing that responseMetadata is KVO-compliant */
  43. @interface PassthroughObserver : NSObject
  44. - (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback
  45. NS_DESIGNATED_INITIALIZER;
  46. - (void)observeValueForKeyPath:(NSString *)keyPath
  47. ofObject:(id)object
  48. change:(NSDictionary *)change
  49. context:(void *)context;
  50. @end
  51. @implementation PassthroughObserver {
  52. void (^_callback)(NSString *, id, NSDictionary *);
  53. }
  54. - (instancetype)init {
  55. return [self initWithCallback:nil];
  56. }
  57. - (instancetype)initWithCallback:(void (^)(NSString *, id, NSDictionary *))callback {
  58. if (!callback) {
  59. return nil;
  60. }
  61. if ((self = [super init])) {
  62. _callback = callback;
  63. }
  64. return self;
  65. }
  66. - (void)observeValueForKeyPath:(NSString *)keyPath
  67. ofObject:(id)object
  68. change:(NSDictionary *)change
  69. context:(void *)context {
  70. _callback(keyPath, object, change);
  71. [object removeObserver:self forKeyPath:keyPath];
  72. }
  73. @end
  74. #pragma mark Tests
  75. /**
  76. * A few tests similar to InteropTests, but which use the generic gRPC client (GRPCCall) rather than
  77. * a generated proto library on top of it. Its RPCs are sent to a local cleartext server.
  78. *
  79. * TODO(jcanizales): Run them also against a local SSL server and against a remote server.
  80. */
  81. @interface GRPCClientTests : XCTestCase
  82. @end
  83. @implementation GRPCClientTests
  84. + (void)setUp {
  85. NSLog(@"GRPCClientTests Started");
  86. }
  87. - (void)setUp {
  88. // Add a custom user agent prefix that will be used in test
  89. [GRPCCall setUserAgentPrefix:@"Foo" forHost:kHostAddress];
  90. // Register test server as non-SSL.
  91. [GRPCCall useInsecureConnectionsForHost:kHostAddress];
  92. // This method isn't implemented by the remote server.
  93. kInexistentMethod =
  94. [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"Inexistent"];
  95. kEmptyCallMethod =
  96. [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"EmptyCall"];
  97. kUnaryCallMethod =
  98. [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"UnaryCall"];
  99. kFullDuplexCallMethod =
  100. [[GRPCProtoMethod alloc] initWithPackage:kPackage service:kService method:@"FullDuplexCall"];
  101. }
  102. - (void)testConnectionToRemoteServer {
  103. __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Server reachable."];
  104. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  105. path:kInexistentMethod.HTTPPath
  106. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  107. id<GRXWriteable> responsesWriteable =
  108. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  109. XCTFail(@"Received unexpected response: %@", value);
  110. }
  111. completionHandler:^(NSError *errorOrNil) {
  112. XCTAssertNotNil(errorOrNil, @"Finished without error!");
  113. XCTAssertEqual(errorOrNil.code, 12, @"Finished with unexpected error: %@", errorOrNil);
  114. [expectation fulfill];
  115. }];
  116. [call startWithWriteable:responsesWriteable];
  117. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  118. }
  119. - (void)testEmptyRPC {
  120. __weak XCTestExpectation *response =
  121. [self expectationWithDescription:@"Empty response received."];
  122. __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
  123. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  124. path:kEmptyCallMethod.HTTPPath
  125. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  126. id<GRXWriteable> responsesWriteable =
  127. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  128. XCTAssertNotNil(value, @"nil value received as response.");
  129. XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
  130. [response fulfill];
  131. }
  132. completionHandler:^(NSError *errorOrNil) {
  133. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  134. [completion fulfill];
  135. }];
  136. [call startWithWriteable:responsesWriteable];
  137. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  138. }
  139. - (void)testSimpleProtoRPC {
  140. __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
  141. __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
  142. RMTSimpleRequest *request = [RMTSimpleRequest message];
  143. request.responseSize = 100;
  144. request.fillUsername = YES;
  145. request.fillOauthScope = YES;
  146. GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
  147. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  148. path:kUnaryCallMethod.HTTPPath
  149. requestsWriter:requestsWriter];
  150. id<GRXWriteable> responsesWriteable =
  151. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  152. XCTAssertNotNil(value, @"nil value received as response.");
  153. XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
  154. RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
  155. // We expect empty strings, not nil:
  156. XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
  157. XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
  158. [response fulfill];
  159. }
  160. completionHandler:^(NSError *errorOrNil) {
  161. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  162. [completion fulfill];
  163. }];
  164. [call startWithWriteable:responsesWriteable];
  165. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  166. }
  167. - (void)testMetadata {
  168. __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
  169. RMTSimpleRequest *request = [RMTSimpleRequest message];
  170. request.fillUsername = YES;
  171. request.fillOauthScope = YES;
  172. GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
  173. GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
  174. path:kUnaryCallMethod.HTTPPath
  175. requestsWriter:requestsWriter];
  176. call.oauth2AccessToken = @"bogusToken";
  177. id<GRXWriteable> responsesWriteable =
  178. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  179. XCTFail(@"Received unexpected response: %@", value);
  180. }
  181. completionHandler:^(NSError *errorOrNil) {
  182. XCTAssertNotNil(errorOrNil, @"Finished without error!");
  183. XCTAssertEqual(errorOrNil.code, 16, @"Finished with unexpected error: %@", errorOrNil);
  184. XCTAssertEqualObjects(call.responseHeaders, errorOrNil.userInfo[kGRPCHeadersKey],
  185. @"Headers in the NSError object and call object differ.");
  186. XCTAssertEqualObjects(call.responseTrailers, errorOrNil.userInfo[kGRPCTrailersKey],
  187. @"Trailers in the NSError object and call object differ.");
  188. NSString *challengeHeader = call.oauth2ChallengeHeader;
  189. XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
  190. call.responseHeaders);
  191. [expectation fulfill];
  192. }];
  193. [call startWithWriteable:responsesWriteable];
  194. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  195. }
  196. - (void)testResponseMetadataKVO {
  197. __weak XCTestExpectation *response =
  198. [self expectationWithDescription:@"Empty response received."];
  199. __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
  200. __weak XCTestExpectation *metadata = [self expectationWithDescription:@"Metadata changed."];
  201. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  202. path:kEmptyCallMethod.HTTPPath
  203. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  204. PassthroughObserver *observer = [[PassthroughObserver alloc]
  205. initWithCallback:^(NSString *keypath, id object, NSDictionary *change) {
  206. if ([keypath isEqual:@"responseHeaders"]) {
  207. [metadata fulfill];
  208. }
  209. }];
  210. [call addObserver:observer forKeyPath:@"responseHeaders" options:0 context:NULL];
  211. id<GRXWriteable> responsesWriteable =
  212. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  213. XCTAssertNotNil(value, @"nil value received as response.");
  214. XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
  215. [response fulfill];
  216. }
  217. completionHandler:^(NSError *errorOrNil) {
  218. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  219. [completion fulfill];
  220. }];
  221. [call startWithWriteable:responsesWriteable];
  222. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  223. }
  224. - (void)testUserAgentPrefix {
  225. __weak XCTestExpectation *response =
  226. [self expectationWithDescription:@"Empty response received."];
  227. __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
  228. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  229. path:kEmptyCallMethod.HTTPPath
  230. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  231. // Setting this special key in the header will cause the interop server to echo back the
  232. // user-agent value, which we confirm.
  233. call.requestHeaders[@"x-grpc-test-echo-useragent"] = @"";
  234. id<GRXWriteable> responsesWriteable =
  235. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  236. XCTAssertNotNil(value, @"nil value received as response.");
  237. XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
  238. NSString *userAgent = call.responseHeaders[@"x-grpc-test-echo-useragent"];
  239. NSError *error = nil;
  240. // Test the regex is correct
  241. NSString *expectedUserAgent = @"Foo grpc-objc/";
  242. expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_OBJC_VERSION_STRING];
  243. expectedUserAgent = [expectedUserAgent stringByAppendingString:@" grpc-c/"];
  244. expectedUserAgent = [expectedUserAgent stringByAppendingString:GRPC_C_VERSION_STRING];
  245. expectedUserAgent = [expectedUserAgent stringByAppendingString:@" (ios; chttp2; "];
  246. expectedUserAgent = [expectedUserAgent
  247. stringByAppendingString:[NSString stringWithUTF8String:grpc_g_stands_for()]];
  248. expectedUserAgent = [expectedUserAgent stringByAppendingString:@")"];
  249. XCTAssertEqualObjects(userAgent, expectedUserAgent);
  250. // Change in format of user-agent field in a direction that does not match the regex will
  251. // likely cause problem for certain gRPC users. For details, refer to internal doc
  252. // https://goo.gl/c2diBc
  253. NSRegularExpression *regex = [NSRegularExpression
  254. regularExpressionWithPattern:@" grpc-[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?/[^ ,]+( \\([^)]*\\))?"
  255. options:0
  256. error:&error];
  257. NSString *customUserAgent =
  258. [regex stringByReplacingMatchesInString:userAgent
  259. options:0
  260. range:NSMakeRange(0, [userAgent length])
  261. withTemplate:@""];
  262. XCTAssertEqualObjects(customUserAgent, @"Foo");
  263. [response fulfill];
  264. }
  265. completionHandler:^(NSError *errorOrNil) {
  266. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  267. [completion fulfill];
  268. }];
  269. [call startWithWriteable:responsesWriteable];
  270. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  271. }
  272. - (void)testTrailers {
  273. __weak XCTestExpectation *response =
  274. [self expectationWithDescription:@"Empty response received."];
  275. __weak XCTestExpectation *completion = [self expectationWithDescription:@"Empty RPC completed."];
  276. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  277. path:kEmptyCallMethod.HTTPPath
  278. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  279. // Setting this special key in the header will cause the interop server to echo back the
  280. // trailer data.
  281. const unsigned char raw_bytes[] = {1, 2, 3, 4};
  282. NSData *trailer_data = [NSData dataWithBytes:raw_bytes length:sizeof(raw_bytes)];
  283. call.requestHeaders[@"x-grpc-test-echo-trailing-bin"] = trailer_data;
  284. id<GRXWriteable> responsesWriteable =
  285. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  286. XCTAssertNotNil(value, @"nil value received as response.");
  287. XCTAssertEqual([value length], 0, @"Non-empty response received: %@", value);
  288. [response fulfill];
  289. }
  290. completionHandler:^(NSError *errorOrNil) {
  291. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  292. XCTAssertEqualObjects((NSData *)call.responseTrailers[@"x-grpc-test-echo-trailing-bin"],
  293. trailer_data, @"Did not receive expected trailer");
  294. [completion fulfill];
  295. }];
  296. [call startWithWriteable:responsesWriteable];
  297. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  298. }
  299. // TODO(makarandd): Move to a different file that contains only unit tests
  300. - (void)testExceptions {
  301. // Try to set parameters to nil for GRPCCall. This should cause an exception
  302. @try {
  303. (void)[[GRPCCall alloc] initWithHost:nil path:nil requestsWriter:nil];
  304. XCTFail(@"Did not receive an exception when parameters are nil");
  305. } @catch (NSException *theException) {
  306. NSLog(@"Received exception as expected: %@", theException.name);
  307. }
  308. // Set state to Finished by force
  309. GRXWriter *requestsWriter = [GRXWriter emptyWriter];
  310. [requestsWriter finishWithError:nil];
  311. @try {
  312. (void)[[GRPCCall alloc] initWithHost:kHostAddress
  313. path:kUnaryCallMethod.HTTPPath
  314. requestsWriter:requestsWriter];
  315. XCTFail(@"Did not receive an exception when GRXWriter has incorrect state.");
  316. } @catch (NSException *theException) {
  317. NSLog(@"Received exception as expected: %@", theException.name);
  318. }
  319. }
  320. - (void)testIdempotentProtoRPC {
  321. __weak XCTestExpectation *response = [self expectationWithDescription:@"Expected response."];
  322. __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
  323. RMTSimpleRequest *request = [RMTSimpleRequest message];
  324. request.responseSize = 100;
  325. request.fillUsername = YES;
  326. request.fillOauthScope = YES;
  327. GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
  328. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  329. path:kUnaryCallMethod.HTTPPath
  330. requestsWriter:requestsWriter];
  331. [GRPCCall setCallSafety:GRPCCallSafetyIdempotentRequest
  332. host:kHostAddress
  333. path:kUnaryCallMethod.HTTPPath];
  334. id<GRXWriteable> responsesWriteable =
  335. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  336. XCTAssertNotNil(value, @"nil value received as response.");
  337. XCTAssertGreaterThan(value.length, 0, @"Empty response received.");
  338. RMTSimpleResponse *responseProto = [RMTSimpleResponse parseFromData:value error:NULL];
  339. // We expect empty strings, not nil:
  340. XCTAssertNotNil(responseProto.username, @"Response's username is nil.");
  341. XCTAssertNotNil(responseProto.oauthScope, @"Response's OAuth scope is nil.");
  342. [response fulfill];
  343. }
  344. completionHandler:^(NSError *errorOrNil) {
  345. XCTAssertNil(errorOrNil, @"Finished with unexpected error: %@", errorOrNil);
  346. [completion fulfill];
  347. }];
  348. [call startWithWriteable:responsesWriteable];
  349. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  350. }
  351. - (void)testAlternateDispatchQueue {
  352. const int32_t kPayloadSize = 100;
  353. RMTSimpleRequest *request = [RMTSimpleRequest message];
  354. request.responseSize = kPayloadSize;
  355. __weak XCTestExpectation *expectation1 =
  356. [self expectationWithDescription:@"AlternateDispatchQueue1"];
  357. // Use default (main) dispatch queue
  358. NSString *main_queue_label =
  359. [NSString stringWithUTF8String:dispatch_queue_get_label(dispatch_get_main_queue())];
  360. GRXWriter *requestsWriter1 = [GRXWriter writerWithValue:[request data]];
  361. GRPCCall *call1 = [[GRPCCall alloc] initWithHost:kHostAddress
  362. path:kUnaryCallMethod.HTTPPath
  363. requestsWriter:requestsWriter1];
  364. id<GRXWriteable> responsesWriteable1 =
  365. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  366. NSString *label =
  367. [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
  368. XCTAssert([label isEqualToString:main_queue_label]);
  369. [expectation1 fulfill];
  370. }
  371. completionHandler:^(NSError *errorOrNil){
  372. }];
  373. [call1 startWithWriteable:responsesWriteable1];
  374. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  375. // Use a custom queue
  376. __weak XCTestExpectation *expectation2 =
  377. [self expectationWithDescription:@"AlternateDispatchQueue2"];
  378. NSString *queue_label = @"test.queue1";
  379. dispatch_queue_t queue = dispatch_queue_create([queue_label UTF8String], DISPATCH_QUEUE_SERIAL);
  380. GRXWriter *requestsWriter2 = [GRXWriter writerWithValue:[request data]];
  381. GRPCCall *call2 = [[GRPCCall alloc] initWithHost:kHostAddress
  382. path:kUnaryCallMethod.HTTPPath
  383. requestsWriter:requestsWriter2];
  384. [call2 setResponseDispatchQueue:queue];
  385. id<GRXWriteable> responsesWriteable2 =
  386. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  387. NSString *label =
  388. [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
  389. XCTAssert([label isEqualToString:queue_label]);
  390. [expectation2 fulfill];
  391. }
  392. completionHandler:^(NSError *errorOrNil){
  393. }];
  394. [call2 startWithWriteable:responsesWriteable2];
  395. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  396. }
  397. - (void)testTimeout {
  398. __weak XCTestExpectation *completion = [self expectationWithDescription:@"RPC completed."];
  399. GRXBufferedPipe *pipe = [GRXBufferedPipe pipe];
  400. GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
  401. path:kFullDuplexCallMethod.HTTPPath
  402. requestsWriter:pipe];
  403. id<GRXWriteable> responsesWriteable =
  404. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  405. XCTAssert(0, @"Failure: response received; Expect: no response received.");
  406. }
  407. completionHandler:^(NSError *errorOrNil) {
  408. XCTAssertNotNil(errorOrNil,
  409. @"Failure: no error received; Expect: receive deadline exceeded.");
  410. XCTAssertEqual(errorOrNil.code, GRPCErrorCodeDeadlineExceeded);
  411. [completion fulfill];
  412. }];
  413. call.timeout = 0.001;
  414. [call startWithWriteable:responsesWriteable];
  415. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  416. }
  417. - (int)findFreePort {
  418. struct sockaddr_in addr;
  419. unsigned int addr_len = sizeof(addr);
  420. memset(&addr, 0, sizeof(addr));
  421. addr.sin_family = AF_INET;
  422. int fd = socket(AF_INET, SOCK_STREAM, 0);
  423. XCTAssertEqual(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), 0);
  424. XCTAssertEqual(getsockname(fd, (struct sockaddr *)&addr, &addr_len), 0);
  425. XCTAssertEqual(addr_len, sizeof(addr));
  426. close(fd);
  427. return addr.sin_port;
  428. }
  429. - (void)testErrorCode {
  430. int port = [self findFreePort];
  431. NSString *const kDummyAddress = [NSString stringWithFormat:@"localhost:%d", port];
  432. __weak XCTestExpectation *completion =
  433. [self expectationWithDescription:@"Received correct error code."];
  434. GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
  435. path:kEmptyCallMethod.HTTPPath
  436. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  437. id<GRXWriteable> responsesWriteable =
  438. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  439. // Should not reach here
  440. XCTAssert(NO);
  441. }
  442. completionHandler:^(NSError *errorOrNil) {
  443. XCTAssertNotNil(errorOrNil, @"Finished with no error");
  444. XCTAssertEqual(errorOrNil.code, GRPC_STATUS_UNAVAILABLE);
  445. [completion fulfill];
  446. }];
  447. [call startWithWriteable:responsesWriteable];
  448. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  449. }
  450. - (void)testTimeoutBackoffWithTimeout:(double)timeout Backoff:(double)backoff {
  451. const double maxConnectTime = timeout > backoff ? timeout : backoff;
  452. const double kMargin = 0.1;
  453. __weak XCTestExpectation *completion = [self expectationWithDescription:@"Timeout in a second."];
  454. NSString *const kDummyAddress = [NSString stringWithFormat:@"8.8.8.8:1"];
  455. GRPCCall *call = [[GRPCCall alloc] initWithHost:kDummyAddress
  456. path:@""
  457. requestsWriter:[GRXWriter writerWithValue:[NSData data]]];
  458. [GRPCCall setMinConnectTimeout:timeout * 1000
  459. initialBackoff:backoff * 1000
  460. maxBackoff:0
  461. forHost:kDummyAddress];
  462. NSDate *startTime = [NSDate date];
  463. id<GRXWriteable> responsesWriteable = [[GRXWriteable alloc] initWithValueHandler:^(id value) {
  464. XCTAssert(NO, @"Received message. Should not reach here");
  465. }
  466. completionHandler:^(NSError *errorOrNil) {
  467. XCTAssertNotNil(errorOrNil, @"Finished with no error");
  468. // The call must fail before maxConnectTime. However there is no lower bound on the time
  469. // taken for connection. A shorter time happens when connection is actively refused
  470. // by 8.8.8.8:1 before maxConnectTime elapsed.
  471. XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:startTime],
  472. maxConnectTime + kMargin);
  473. [completion fulfill];
  474. }];
  475. [call startWithWriteable:responsesWriteable];
  476. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  477. }
  478. // The numbers of the following three tests are selected to be smaller than the default values of
  479. // initial backoff (1s) and min_connect_timeout (20s), so that if they fail we know the default
  480. // values fail to be overridden by the channel args.
  481. - (void)testTimeoutBackoff2 {
  482. [self testTimeoutBackoffWithTimeout:0.7 Backoff:0.3];
  483. }
  484. - (void)testTimeoutBackoff3 {
  485. [self testTimeoutBackoffWithTimeout:0.3 Backoff:0.7];
  486. }
  487. - (void)testErrorDebugInformation {
  488. __weak XCTestExpectation *expectation = [self expectationWithDescription:@"RPC unauthorized."];
  489. RMTSimpleRequest *request = [RMTSimpleRequest message];
  490. request.fillUsername = YES;
  491. request.fillOauthScope = YES;
  492. GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
  493. GRPCCall *call = [[GRPCCall alloc] initWithHost:kRemoteSSLHost
  494. path:kUnaryCallMethod.HTTPPath
  495. requestsWriter:requestsWriter];
  496. call.oauth2AccessToken = @"bogusToken";
  497. id<GRXWriteable> responsesWriteable =
  498. [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
  499. XCTFail(@"Received unexpected response: %@", value);
  500. }
  501. completionHandler:^(NSError *errorOrNil) {
  502. XCTAssertNotNil(errorOrNil, @"Finished without error!");
  503. NSDictionary *userInfo = errorOrNil.userInfo;
  504. NSString *debugInformation = userInfo[NSDebugDescriptionErrorKey];
  505. XCTAssertNotNil(debugInformation);
  506. XCTAssertNotEqual([debugInformation length], 0);
  507. NSString *challengeHeader = call.oauth2ChallengeHeader;
  508. XCTAssertGreaterThan(challengeHeader.length, 0, @"No challenge in response headers %@",
  509. call.responseHeaders);
  510. [expectation fulfill];
  511. }];
  512. [call startWithWriteable:responsesWriteable];
  513. [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
  514. }
  515. @end