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