aboutsummaryrefslogtreecommitdiff
path: root/objectivec/Tests/GPBCodedOuputStreamTests.m
diff options
context:
space:
mode:
Diffstat (limited to 'objectivec/Tests/GPBCodedOuputStreamTests.m')
-rw-r--r--objectivec/Tests/GPBCodedOuputStreamTests.m119
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