diff options
Diffstat (limited to 'objectivec/Tests/GPBCodedOuputStreamTests.m')
-rw-r--r-- | objectivec/Tests/GPBCodedOuputStreamTests.m | 119 |
1 files changed, 117 insertions, 2 deletions
diff --git a/objectivec/Tests/GPBCodedOuputStreamTests.m b/objectivec/Tests/GPBCodedOuputStreamTests.m index 77d88033..109239d5 100644 --- a/objectivec/Tests/GPBCodedOuputStreamTests.m +++ b/objectivec/Tests/GPBCodedOuputStreamTests.m @@ -30,11 +30,30 @@ #import "GPBTestUtilities.h" -#import "GPBCodedOutputStream.h" +#import "GPBCodedOutputStream_PackagePrivate.h" #import "GPBCodedInputStream.h" #import "GPBUtilities_PackagePrivate.h" #import "google/protobuf/Unittest.pbobjc.h" +@interface GPBCodedOutputStream (InternalMethods) +// Declared in the .m file, expose for testing. +- (instancetype)initWithOutputStream:(NSOutputStream *)output + data:(NSMutableData *)data; +@end + +@interface GPBCodedOutputStream (Helper) ++ (instancetype)streamWithOutputStream:(NSOutputStream *)output + bufferSize:(size_t)bufferSize; +@end + +@implementation GPBCodedOutputStream (Helper) ++ (instancetype)streamWithOutputStream:(NSOutputStream *)output + bufferSize:(size_t)bufferSize { + NSMutableData *data = [NSMutableData dataWithLength:bufferSize]; + return [[[self alloc] initWithOutputStream:output data:data] autorelease]; +} +@end + @interface CodedOutputStreamTests : GPBTestCase @end @@ -174,6 +193,32 @@ } } +- (void)assertWriteStringNoTag:(NSData*)data + value:(NSString *)value + context:(NSString *)contextMessage { + NSOutputStream* rawOutput = [NSOutputStream outputStreamToMemory]; + GPBCodedOutputStream* output = + [GPBCodedOutputStream streamWithOutputStream:rawOutput]; + [output writeStringNoTag:value]; + [output flush]; + + NSData* actual = + [rawOutput propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + XCTAssertEqualObjects(data, actual, @"%@", contextMessage); + + // Try different block sizes. + for (int blockSize = 1; blockSize <= 16; blockSize *= 2) { + rawOutput = [NSOutputStream outputStreamToMemory]; + output = [GPBCodedOutputStream streamWithOutputStream:rawOutput + bufferSize:blockSize]; + [output writeStringNoTag:value]; + [output flush]; + + actual = [rawOutput propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + XCTAssertEqualObjects(data, actual, @"%@", contextMessage); + } +} + - (void)testWriteVarint1 { [self assertWriteVarint:bytes(0x00) value:0]; } @@ -221,7 +266,7 @@ value:(0x1b << 0) | (0x28 << 7) | (0x79 << 14) | (0x42 << 21) | (0x3bLL << 28) | (0x56LL << 35) | (0x00LL << 42) | (0x05LL << 49) | (0x26LL << 56) | - (0x01LL << 63)]; + (0x01ULL << 63)]; } - (void)testWriteLittleEndian { @@ -318,4 +363,74 @@ XCTAssertEqualObjects(rawBytes, goldenData); } +- (void)testCFStringGetCStringPtrAndStringsWithNullChars { + // This test exists to verify that CFStrings with embedded NULLs still expose + // their raw buffer if they are backed by UTF8 storage. If this fails, the + // quick/direct access paths in GPBCodedOutputStream that depend on + // CFStringGetCStringPtr need to be re-evalutated (maybe just removed). + // And yes, we do get NULLs in strings from some servers. + + char zeroTest[] = "\0Test\0String"; + // Note: there is a \0 at the end of this since it is a c-string. + NSString *asNSString = [[NSString alloc] initWithBytes:zeroTest + length:sizeof(zeroTest) + encoding:NSUTF8StringEncoding]; + const char *cString = + CFStringGetCStringPtr((CFStringRef)asNSString, kCFStringEncodingUTF8); + XCTAssertTrue(cString != NULL); + // Again, if the above assert fails, then it means NSString no longer exposes + // the raw utf8 storage of a string created from utf8 input, so the code using + // CFStringGetCStringPtr in GPBCodedOutputStream will still work (it will take + // a different code path); but the optimizations for when + // CFStringGetCStringPtr does work could possibly go away. + + XCTAssertEqual(sizeof(zeroTest), + [asNSString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); + XCTAssertTrue(0 == memcmp(cString, zeroTest, sizeof(zeroTest))); + [asNSString release]; +} + +- (void)testWriteStringsWithZeroChar { + // Unicode allows `\0` as a character, and NSString is a class cluster, so + // there are a few different classes that could end up beind a given string. + // Historically, we've seen differences based on constant strings in code and + // strings built via the NSString apis. So this round trips them to ensure + // they are acting as expected. + + NSArray<NSString *> *strs = @[ + @"\0at start", + @"in\0middle", + @"at end\0", + ]; + int i = 0; + for (NSString *str in strs) { + NSData *asUTF8 = [str dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *expected = [NSMutableData data]; + uint8_t lengthByte = (uint8_t)asUTF8.length; + [expected appendBytes:&lengthByte length:1]; + [expected appendData:asUTF8]; + + NSString *context = [NSString stringWithFormat:@"Loop %d - Literal", i]; + [self assertWriteStringNoTag:expected value:str context:context]; + + // Force a new string to be built which gets a different class from the + // NSString class cluster than the literal did. + NSString *str2 = [NSString stringWithFormat:@"%@", str]; + context = [NSString stringWithFormat:@"Loop %d - Built", i]; + [self assertWriteStringNoTag:expected value:str2 context:context]; + + ++i; + } +} + +- (void)testThatItThrowsWhenWriteRawPtrFails { + NSOutputStream *output = [NSOutputStream outputStreamToMemory]; + GPBCodedOutputStream *codedOutput = + [GPBCodedOutputStream streamWithOutputStream:output bufferSize:0]; // Skip buffering. + [output close]; // Close the output stream to force failure on write. + const char *cString = "raw"; + XCTAssertThrowsSpecificNamed([codedOutput writeRawPtr:cString offset:0 length:strlen(cString)], + NSException, GPBCodedOutputStreamException_WriteFailed); +} + @end |