GRPCClientTests.m 26 KB

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