From 988ffe0a78ebda0410e61ce31a3bd689c774f59e Mon Sep 17 00:00:00 2001 From: Thomas Van Lenten Date: Wed, 4 Jan 2017 15:03:42 -0500 Subject: [PATCH] Minor fix for autocreated object repeated fields and maps. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - If setting/clearing a repeated field/map that was objects, check the class before checking the autocreator. - Just to be paranoid, don’t mutate within copy/mutableCopy for the autocreated classes to ensure there is less chance of issues if someone does something really crazy threading wise. - Some more tests for the internal AutocreatedArray/AutocreatedDictionary classes to ensure things are working as expected. - Add Xcode 8.2 to the full_mac_build.sh supported list. --- Makefile.am | 1 + objectivec/DevTools/full_mac_build.sh | 8 + objectivec/GPBArray.m | 4 +- objectivec/GPBDictionary.m | 8 +- objectivec/GPBMessage.m | 16 +- objectivec/GPBUtilities.m | 16 +- .../project.pbxproj | 4 + .../project.pbxproj | 4 + objectivec/Tests/GPBArrayTests.m | 173 ++++++++++++++++ objectivec/Tests/GPBDictionaryTests.m | 186 ++++++++++++++++++ objectivec/Tests/GPBMessageTests.m | 31 +++ 11 files changed, 435 insertions(+), 16 deletions(-) create mode 100644 objectivec/Tests/GPBDictionaryTests.m diff --git a/Makefile.am b/Makefile.am index 4ece5217b9..5ade9d2bac 100644 --- a/Makefile.am +++ b/Makefile.am @@ -525,6 +525,7 @@ objectivec_EXTRA_DIST= \ objectivec/Tests/GPBDictionaryTests+String.m \ objectivec/Tests/GPBDictionaryTests+UInt32.m \ objectivec/Tests/GPBDictionaryTests+UInt64.m \ + objectivec/Tests/GPBDictionaryTests.m \ objectivec/Tests/GPBDictionaryTests.pddm \ objectivec/Tests/GPBMessageTests+Merge.m \ objectivec/Tests/GPBMessageTests+Runtime.m \ diff --git a/objectivec/DevTools/full_mac_build.sh b/objectivec/DevTools/full_mac_build.sh index ef8fb74089..ea9fd2736b 100755 --- a/objectivec/DevTools/full_mac_build.sh +++ b/objectivec/DevTools/full_mac_build.sh @@ -265,6 +265,14 @@ if [[ "${DO_XCODE_IOS_TESTS}" == "yes" ]] ; then -destination "platform=iOS Simulator,name=iPad Pro (9.7 inch),OS=10.1" # 64bit ) ;; + 8.2* ) + XCODEBUILD_TEST_BASE_IOS+=( + -destination "platform=iOS Simulator,name=iPhone 4s,OS=8.1" # 32bit + -destination "platform=iOS Simulator,name=iPhone 7,OS=10.2" # 64bit + -destination "platform=iOS Simulator,name=iPad 2,OS=8.1" # 32bit + -destination "platform=iOS Simulator,name=iPad Pro (9.7 inch),OS=10.2" # 64bit + ) + ;; * ) echo "Time to update the simulator targets for Xcode ${XCODE_VERSION}" exit 2 diff --git a/objectivec/GPBArray.m b/objectivec/GPBArray.m index ae57747dc9..f401631d12 100644 --- a/objectivec/GPBArray.m +++ b/objectivec/GPBArray.m @@ -2519,14 +2519,14 @@ static BOOL ArrayDefault_IsValidValue(int32_t value) { - (id)copyWithZone:(NSZone *)zone { if (_array == nil) { - _array = [[NSMutableArray alloc] init]; + return [[NSMutableArray allocWithZone:zone] init]; } return [_array copyWithZone:zone]; } - (id)mutableCopyWithZone:(NSZone *)zone { if (_array == nil) { - _array = [[NSMutableArray alloc] init]; + return [[NSMutableArray allocWithZone:zone] init]; } return [_array mutableCopyWithZone:zone]; } diff --git a/objectivec/GPBDictionary.m b/objectivec/GPBDictionary.m index fd8bd1ce77..1c67c680e9 100644 --- a/objectivec/GPBDictionary.m +++ b/objectivec/GPBDictionary.m @@ -13579,22 +13579,26 @@ void GPBDictionaryReadEntry(id mapDictionary, - (id)copyWithZone:(NSZone *)zone { if (_dictionary == nil) { - _dictionary = [[NSMutableDictionary alloc] init]; + return [[NSMutableDictionary allocWithZone:zone] init]; } return [_dictionary copyWithZone:zone]; } - (id)mutableCopyWithZone:(NSZone *)zone { if (_dictionary == nil) { - _dictionary = [[NSMutableDictionary alloc] init]; + return [[NSMutableDictionary allocWithZone:zone] init]; } return [_dictionary mutableCopyWithZone:zone]; } +// Not really needed, but subscripting is likely common enough it doesn't hurt +// to ensure it goes directly to the real NSMutableDictionary. - (id)objectForKeyedSubscript:(id)key { return [_dictionary objectForKeyedSubscript:key]; } +// Not really needed, but subscripting is likely common enough it doesn't hurt +// to ensure it goes directly to the real NSMutableDictionary. - (void)setObject:(id)obj forKeyedSubscript:(id)key { if (_dictionary == nil) { _dictionary = [[NSMutableDictionary alloc] init]; diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m index 4a6f0612ac..9660f1edff 100644 --- a/objectivec/GPBMessage.m +++ b/objectivec/GPBMessage.m @@ -1023,9 +1023,11 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) { if (arrayOrMap) { if (field.fieldType == GPBFieldTypeRepeated) { if (GPBFieldDataTypeIsObject(field)) { - GPBAutocreatedArray *autoArray = arrayOrMap; - if (autoArray->_autocreator == self) { - autoArray->_autocreator = nil; + if ([arrayOrMap isKindOfClass:[GPBAutocreatedArray class]]) { + GPBAutocreatedArray *autoArray = arrayOrMap; + if (autoArray->_autocreator == self) { + autoArray->_autocreator = nil; + } } } else { // Type doesn't matter, it is a GPB*Array. @@ -1037,9 +1039,11 @@ static GPBUnknownFieldSet *GetOrMakeUnknownFields(GPBMessage *self) { } else { if ((field.mapKeyDataType == GPBDataTypeString) && GPBFieldDataTypeIsObject(field)) { - GPBAutocreatedDictionary *autoDict = arrayOrMap; - if (autoDict->_autocreator == self) { - autoDict->_autocreator = nil; + if ([arrayOrMap isKindOfClass:[GPBAutocreatedDictionary class]]) { + GPBAutocreatedDictionary *autoDict = arrayOrMap; + if (autoDict->_autocreator == self) { + autoDict->_autocreator = nil; + } } } else { // Type doesn't matter, it is a GPB*Dictionary. diff --git a/objectivec/GPBUtilities.m b/objectivec/GPBUtilities.m index d4538598f7..68aadb771e 100644 --- a/objectivec/GPBUtilities.m +++ b/objectivec/GPBUtilities.m @@ -408,9 +408,11 @@ void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self, if (field.fieldType == GPBFieldTypeRepeated) { // If the old array was autocreated by us, then clear it. if (GPBDataTypeIsObject(fieldType)) { - GPBAutocreatedArray *autoArray = oldValue; - if (autoArray->_autocreator == self) { - autoArray->_autocreator = nil; + if ([oldValue isKindOfClass:[GPBAutocreatedArray class]]) { + GPBAutocreatedArray *autoArray = oldValue; + if (autoArray->_autocreator == self) { + autoArray->_autocreator = nil; + } } } else { // Type doesn't matter, it is a GPB*Array. @@ -423,9 +425,11 @@ void GPBSetRetainedObjectIvarWithFieldInternal(GPBMessage *self, // If the old map was autocreated by us, then clear it. if ((field.mapKeyDataType == GPBDataTypeString) && GPBDataTypeIsObject(fieldType)) { - GPBAutocreatedDictionary *autoDict = oldValue; - if (autoDict->_autocreator == self) { - autoDict->_autocreator = nil; + if ([oldValue isKindOfClass:[GPBAutocreatedDictionary class]]) { + GPBAutocreatedDictionary *autoDict = oldValue; + if (autoDict->_autocreator == self) { + autoDict->_autocreator = nil; + } } } else { // Type doesn't matter, it is a GPB*Dictionary. diff --git a/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj b/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj index 7ce5d54f86..919d007642 100644 --- a/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj +++ b/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ F47476E51D21A524007C7B1A /* Duration.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B4248D41A92826400BC1EC6 /* Duration.pbobjc.m */; }; F47476E61D21A524007C7B1A /* Timestamp.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B4248D61A92826400BC1EC6 /* Timestamp.pbobjc.m */; }; F4B51B1E1BBC610700744318 /* GPBObjectiveCPlusPlusTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4B51B1D1BBC610700744318 /* GPBObjectiveCPlusPlusTest.mm */; }; + F4C4B9E41E1D976300D3B61D /* GPBDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F4C4B9E21E1D974F00D3B61D /* GPBDictionaryTests.m */; }; F4E675971B21D0000054530B /* Any.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E675871B21D0000054530B /* Any.pbobjc.m */; }; F4E675991B21D0000054530B /* Api.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E675891B21D0000054530B /* Api.pbobjc.m */; }; F4E6759B1B21D0000054530B /* Empty.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E6758B1B21D0000054530B /* Empty.pbobjc.m */; }; @@ -187,6 +188,7 @@ F4B6B8B21A9CCBDA00892426 /* GPBUnknownFieldSet_PackagePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBUnknownFieldSet_PackagePrivate.h; sourceTree = ""; }; F4B6B8B61A9CD1DE00892426 /* GPBExtensionInternals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBExtensionInternals.h; sourceTree = ""; }; F4B6B8B81A9CD1DE00892426 /* GPBRootObject_PackagePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBRootObject_PackagePrivate.h; sourceTree = ""; }; + F4C4B9E21E1D974F00D3B61D /* GPBDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBDictionaryTests.m; sourceTree = ""; }; F4CF31701B162ED800BD9B06 /* unittest_objc_startup.proto */ = {isa = PBXFileReference; lastKnownFileType = text; path = unittest_objc_startup.proto; sourceTree = ""; }; F4E675861B21D0000054530B /* Any.pbobjc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Any.pbobjc.h; path = google/protobuf/Any.pbobjc.h; sourceTree = ""; }; F4E675871B21D0000054530B /* Any.pbobjc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Any.pbobjc.m; path = google/protobuf/Any.pbobjc.m; sourceTree = ""; }; @@ -393,6 +395,7 @@ 7461B69D0F94FDF800A0C422 /* GPBCodedOuputStreamTests.m */, 5102DABB1891A052002037B6 /* GPBConcurrencyTests.m */, F4353D1C1AB8822D005A6198 /* GPBDescriptorTests.m */, + F4C4B9E21E1D974F00D3B61D /* GPBDictionaryTests.m */, F4353D2C1AC06F10005A6198 /* GPBDictionaryTests.pddm */, F4353D2D1AC06F10005A6198 /* GPBDictionaryTests+Bool.m */, F4353D2E1AC06F10005A6198 /* GPBDictionaryTests+Int32.m */, @@ -677,6 +680,7 @@ F4353D371AC06F10005A6198 /* GPBDictionaryTests+String.m in Sources */, F4353D381AC06F10005A6198 /* GPBDictionaryTests+UInt32.m in Sources */, 8BBEA4B7147C727D00C4ADB7 /* GPBUtilitiesTests.m in Sources */, + F4C4B9E41E1D976300D3B61D /* GPBDictionaryTests.m in Sources */, 8BBEA4B8147C727D00C4ADB7 /* GPBWireFormatTests.m in Sources */, 8BD3981F14BE59D70081D629 /* GPBUnittestProtos.m in Sources */, 8B8B615D17DF7056002EE618 /* GPBARCUnittestProtos.m in Sources */, diff --git a/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj b/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj index 5f599719c5..64fc45c0e2 100644 --- a/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj +++ b/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj @@ -65,6 +65,7 @@ F47476E91D21A537007C7B1A /* Duration.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B4248DE1A929C7D00BC1EC6 /* Duration.pbobjc.m */; }; F47476EA1D21A537007C7B1A /* Timestamp.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 8B4248E01A929C7D00BC1EC6 /* Timestamp.pbobjc.m */; }; F4B51B1C1BBC5C7100744318 /* GPBObjectiveCPlusPlusTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4B51B1B1BBC5C7100744318 /* GPBObjectiveCPlusPlusTest.mm */; }; + F4C4B9E71E1D97BF00D3B61D /* GPBDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F4C4B9E51E1D97BB00D3B61D /* GPBDictionaryTests.m */; }; F4E675D01B21D1620054530B /* Any.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E675B71B21D1440054530B /* Any.pbobjc.m */; }; F4E675D11B21D1620054530B /* Api.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E675B91B21D1440054530B /* Api.pbobjc.m */; }; F4E675D21B21D1620054530B /* Empty.pbobjc.m in Sources */ = {isa = PBXBuildFile; fileRef = F4E675BC1B21D1440054530B /* Empty.pbobjc.m */; }; @@ -209,6 +210,7 @@ F4B6B8B11A9CCBBB00892426 /* GPBUnknownFieldSet_PackagePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBUnknownFieldSet_PackagePrivate.h; sourceTree = ""; }; F4B6B8B31A9CD1C600892426 /* GPBExtensionInternals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBExtensionInternals.h; sourceTree = ""; }; F4B6B8B51A9CD1C600892426 /* GPBRootObject_PackagePrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GPBRootObject_PackagePrivate.h; sourceTree = ""; }; + F4C4B9E51E1D97BB00D3B61D /* GPBDictionaryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GPBDictionaryTests.m; sourceTree = ""; }; F4CF31711B162EF500BD9B06 /* unittest_objc_startup.proto */ = {isa = PBXFileReference; lastKnownFileType = text; path = unittest_objc_startup.proto; sourceTree = ""; }; F4E675B61B21D1440054530B /* Any.pbobjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Any.pbobjc.h; path = google/protobuf/Any.pbobjc.h; sourceTree = ""; }; F4E675B71B21D1440054530B /* Any.pbobjc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = Any.pbobjc.m; path = google/protobuf/Any.pbobjc.m; sourceTree = ""; }; @@ -431,6 +433,7 @@ 7461B69D0F94FDF800A0C422 /* GPBCodedOuputStreamTests.m */, 5102DABB1891A052002037B6 /* GPBConcurrencyTests.m */, F4353D1E1AB88243005A6198 /* GPBDescriptorTests.m */, + F4C4B9E51E1D97BB00D3B61D /* GPBDictionaryTests.m */, F4353D3A1AC06F31005A6198 /* GPBDictionaryTests.pddm */, F4353D3B1AC06F31005A6198 /* GPBDictionaryTests+Bool.m */, F4353D3C1AC06F31005A6198 /* GPBDictionaryTests+Int32.m */, @@ -773,6 +776,7 @@ F4353D451AC06F31005A6198 /* GPBDictionaryTests+String.m in Sources */, F4353D461AC06F31005A6198 /* GPBDictionaryTests+UInt32.m in Sources */, 8BBEA4B7147C727D00C4ADB7 /* GPBUtilitiesTests.m in Sources */, + F4C4B9E71E1D97BF00D3B61D /* GPBDictionaryTests.m in Sources */, 8BBEA4B8147C727D00C4ADB7 /* GPBWireFormatTests.m in Sources */, 8BD3981F14BE59D70081D629 /* GPBUnittestProtos.m in Sources */, 8B8B615D17DF7056002EE618 /* GPBARCUnittestProtos.m in Sources */, diff --git a/objectivec/Tests/GPBArrayTests.m b/objectivec/Tests/GPBArrayTests.m index 0fb15e405f..31f75501dc 100644 --- a/objectivec/Tests/GPBArrayTests.m +++ b/objectivec/Tests/GPBArrayTests.m @@ -32,6 +32,7 @@ #import #import "GPBArray.h" +#import "GPBArray_PackagePrivate.h" #import "GPBTestUtilities.h" @@ -3436,3 +3437,175 @@ static BOOL TestingEnum_IsValidValue2(int32_t value) { } @end + +#pragma mark - GPBAutocreatedArray Tests + +// These are hand written tests to double check some behaviors of the +// GPBAutocreatedArray. + +// NOTE: GPBAutocreatedArray is private to the library, users of the library +// should never have to directly deal with this class. + +@interface GPBAutocreatedArrayTests : XCTestCase +@end + +@implementation GPBAutocreatedArrayTests + +- (void)testEquality { + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + + XCTAssertTrue([array isEqual:@[]]); + XCTAssertTrue([array isEqualToArray:@[]]); + + XCTAssertFalse([array isEqual:@[ @"foo" ]]); + XCTAssertFalse([array isEqualToArray:@[ @"foo" ]]); + + [array addObject:@"foo"]; + + XCTAssertFalse([array isEqual:@[]]); + XCTAssertFalse([array isEqualToArray:@[]]); + XCTAssertTrue([array isEqual:@[ @"foo" ]]); + XCTAssertTrue([array isEqualToArray:@[ @"foo" ]]); + XCTAssertFalse([array isEqual:@[ @"bar" ]]); + XCTAssertFalse([array isEqualToArray:@[ @"bar" ]]); + + GPBAutocreatedArray *array2 = [[GPBAutocreatedArray alloc] init]; + + XCTAssertFalse([array isEqual:array2]); + XCTAssertFalse([array isEqualToArray:array2]); + + [array2 addObject:@"bar"]; + XCTAssertFalse([array isEqual:array2]); + XCTAssertFalse([array isEqualToArray:array2]); + + [array2 replaceObjectAtIndex:0 withObject:@"foo"]; + XCTAssertTrue([array isEqual:array2]); + XCTAssertTrue([array isEqualToArray:array2]); + + [array2 release]; + [array release]; +} + +- (void)testCopy { + { + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + + NSArray *cpy = [array copy]; + XCTAssertTrue(cpy != array); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy.count, (NSUInteger)0); + + NSArray *cpy2 = [array copy]; + XCTAssertTrue(cpy2 != array); // Ptr compare + // Can't compare cpy and cpy2 because NSArray has a singleton empty + // array it uses, so the ptrs are the same. + XCTAssertTrue([cpy2 isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)0); + + [cpy2 release]; + [cpy release]; + [array release]; + } + + { + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + + NSMutableArray *cpy = [array mutableCopy]; + XCTAssertTrue(cpy != array); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSMutableArray class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy.count, (NSUInteger)0); + + NSMutableArray *cpy2 = [array mutableCopy]; + XCTAssertTrue(cpy2 != array); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSMutableArray class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)0); + + [cpy2 release]; + [cpy release]; + [array release]; + } + + { + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + [array addObject:@"foo"]; + [array addObject:@"bar"]; + + NSArray *cpy = [array copy]; + XCTAssertTrue(cpy != array); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy[0], @"foo"); + XCTAssertEqualObjects(cpy[1], @"bar"); + + NSArray *cpy2 = [array copy]; + XCTAssertTrue(cpy2 != array); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy2[0], @"foo"); + XCTAssertEqualObjects(cpy2[1], @"bar"); + + [cpy2 release]; + [cpy release]; + [array release]; + } + + { + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + [array addObject:@"foo"]; + [array addObject:@"bar"]; + + NSMutableArray *cpy = [array mutableCopy]; + XCTAssertTrue(cpy != array); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy[0], @"foo"); + XCTAssertEqualObjects(cpy[1], @"bar"); + + NSMutableArray *cpy2 = [array mutableCopy]; + XCTAssertTrue(cpy2 != array); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSArray class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedArray class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy2[0], @"foo"); + XCTAssertEqualObjects(cpy2[1], @"bar"); + + [cpy2 release]; + [cpy release]; + [array release]; + } +} + +- (void)testIndexedSubscriptSupport { + // The base NSArray/NSMutableArray behaviors for *IndexedSubscript methods + // should still work via the methods that one has to override to make an + // NSMutableArray subclass. i.e. - this should "just work" and if these + // crash/fail, then something is wrong in how NSMutableArray is subclassed. + + GPBAutocreatedArray *array = [[GPBAutocreatedArray alloc] init]; + + [array addObject:@"foo"]; + [array addObject:@"bar"]; + XCTAssertEqual(array.count, (NSUInteger)2); + XCTAssertEqualObjects(array[0], @"foo"); + XCTAssertEqualObjects(array[1], @"bar"); + array[0] = @"foo2"; + array[2] = @"baz"; + XCTAssertEqual(array.count, (NSUInteger)3); + XCTAssertEqualObjects(array[0], @"foo2"); + XCTAssertEqualObjects(array[1], @"bar"); + XCTAssertEqualObjects(array[2], @"baz"); + + [array release]; +} + +@end diff --git a/objectivec/Tests/GPBDictionaryTests.m b/objectivec/Tests/GPBDictionaryTests.m new file mode 100644 index 0000000000..52b4b32806 --- /dev/null +++ b/objectivec/Tests/GPBDictionaryTests.m @@ -0,0 +1,186 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2017 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#import +#import + +#import "GPBDictionary.h" +#import "GPBDictionary_PackagePrivate.h" + +#import "GPBTestUtilities.h" + +#pragma mark - GPBAutocreatedDictionary Tests + +// These are hand written tests to double check some behaviors of the +// GPBAutocreatedDictionary. The GPBDictionary+[type]Tests files are generate +// tests. + +// NOTE: GPBAutocreatedDictionary is private to the library, users of the +// library should never have to directly deal with this class. + +@interface GPBAutocreatedDictionaryTests : XCTestCase +@end + +@implementation GPBAutocreatedDictionaryTests + +- (void)testEquality { + GPBAutocreatedDictionary *dict = [[GPBAutocreatedDictionary alloc] init]; + + XCTAssertTrue([dict isEqual:@{}]); + XCTAssertTrue([dict isEqualToDictionary:@{}]); + + XCTAssertFalse([dict isEqual:@{ @"foo" : @"bar" }]); + XCTAssertFalse([dict isEqualToDictionary:@{ @"foo" : @"bar" }]); + + [dict setObject:@"bar" forKey:@"foo"]; + + XCTAssertFalse([dict isEqual:@{}]); + XCTAssertFalse([dict isEqualToDictionary:@{}]); + XCTAssertTrue([dict isEqual:@{ @"foo" : @"bar" }]); + XCTAssertTrue([dict isEqualToDictionary:@{ @"foo" : @"bar" }]); + XCTAssertFalse([dict isEqual:@{ @"bar" : @"baz" }]); + XCTAssertFalse([dict isEqualToDictionary:@{ @"bar" : @"baz" }]); + + GPBAutocreatedDictionary *dict2 = [[GPBAutocreatedDictionary alloc] init]; + + XCTAssertFalse([dict isEqual:dict2]); + XCTAssertFalse([dict isEqualToDictionary:dict2]); + + [dict2 setObject:@"mumble" forKey:@"foo"]; + XCTAssertFalse([dict isEqual:dict2]); + XCTAssertFalse([dict isEqualToDictionary:dict2]); + + [dict2 setObject:@"bar" forKey:@"foo"]; + XCTAssertTrue([dict isEqual:dict2]); + XCTAssertTrue([dict isEqualToDictionary:dict2]); + + [dict2 release]; + [dict release]; +} + +- (void)testCopy { + { + GPBAutocreatedDictionary *dict = [[GPBAutocreatedDictionary alloc] init]; + + NSDictionary *cpy = [dict copy]; + XCTAssertTrue(cpy != dict); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSDictionary class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy.count, (NSUInteger)0); + + NSDictionary *cpy2 = [dict copy]; + XCTAssertTrue(cpy2 != dict); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSDictionary class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)0); + + [cpy2 release]; + [cpy release]; + [dict release]; + } + + { + GPBAutocreatedDictionary *dict = [[GPBAutocreatedDictionary alloc] init]; + + NSMutableDictionary *cpy = [dict mutableCopy]; + XCTAssertTrue(cpy != dict); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSMutableDictionary class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy.count, (NSUInteger)0); + + NSMutableDictionary *cpy2 = [dict mutableCopy]; + XCTAssertTrue(cpy2 != dict); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSMutableDictionary class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)0); + + [cpy2 release]; + [cpy release]; + [dict release]; + } + + { + GPBAutocreatedDictionary *dict = [[GPBAutocreatedDictionary alloc] init]; + dict[@"foo"] = @"bar"; + dict[@"baz"] = @"mumble"; + + NSDictionary *cpy = [dict copy]; + XCTAssertTrue(cpy != dict); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSDictionary class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy[@"foo"], @"bar"); + XCTAssertEqualObjects(cpy[@"baz"], @"mumble"); + + NSDictionary *cpy2 = [dict copy]; + XCTAssertTrue(cpy2 != dict); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSDictionary class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy2[@"foo"], @"bar"); + XCTAssertEqualObjects(cpy2[@"baz"], @"mumble"); + + [cpy2 release]; + [cpy release]; + [dict release]; + } + + { + GPBAutocreatedDictionary *dict = [[GPBAutocreatedDictionary alloc] init]; + dict[@"foo"] = @"bar"; + dict[@"baz"] = @"mumble"; + + NSMutableDictionary *cpy = [dict mutableCopy]; + XCTAssertTrue(cpy != dict); // Ptr compare + XCTAssertTrue([cpy isKindOfClass:[NSMutableDictionary class]]); + XCTAssertFalse([cpy isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy[@"foo"], @"bar"); + XCTAssertEqualObjects(cpy[@"baz"], @"mumble"); + + NSMutableDictionary *cpy2 = [dict mutableCopy]; + XCTAssertTrue(cpy2 != dict); // Ptr compare + XCTAssertTrue(cpy2 != cpy); // Ptr compare + XCTAssertTrue([cpy2 isKindOfClass:[NSMutableDictionary class]]); + XCTAssertFalse([cpy2 isKindOfClass:[GPBAutocreatedDictionary class]]); + XCTAssertEqual(cpy2.count, (NSUInteger)2); + XCTAssertEqualObjects(cpy2[@"foo"], @"bar"); + XCTAssertEqualObjects(cpy2[@"baz"], @"mumble"); + + [cpy2 release]; + [cpy release]; + [dict release]; + } +} + +@end diff --git a/objectivec/Tests/GPBMessageTests.m b/objectivec/Tests/GPBMessageTests.m index a3c5a6b453..c15535c508 100644 --- a/objectivec/Tests/GPBMessageTests.m +++ b/objectivec/Tests/GPBMessageTests.m @@ -1082,6 +1082,20 @@ [repeatedStringArray release]; } +- (void)testSetOverAutocreatedArrayAndSetAgain { + // Ensure when dealing with replacing an array it is handled being either + // an autocreated one or a straight NSArray. + + // The real test here is that nothing crashes while doing the work. + TestAllTypes *message = [TestAllTypes message]; + [message.repeatedStringArray addObject:@"foo"]; + XCTAssertEqual(message.repeatedStringArray_Count, (NSUInteger)1); + message.repeatedStringArray = [NSMutableArray arrayWithObjects:@"bar", @"bar2", nil]; + XCTAssertEqual(message.repeatedStringArray_Count, (NSUInteger)2); + message.repeatedStringArray = [NSMutableArray arrayWithObject:@"baz"]; + XCTAssertEqual(message.repeatedStringArray_Count, (NSUInteger)1); +} + - (void)testReplaceAutocreatedArray { // Replacing array should orphan the old one and cause its creator to become // visible. @@ -1281,6 +1295,23 @@ [strToStr release]; } +- (void)testSetOverAutocreatedMapAndSetAgain { + // Ensure when dealing with replacing a map it is handled being either + // an autocreated one or a straight NSDictionary. + + // The real test here is that nothing crashes while doing the work. + TestRecursiveMessageWithRepeatedField *message = + [TestRecursiveMessageWithRepeatedField message]; + message.strToStr[@"foo"] = @"bar"; + XCTAssertEqual(message.strToStr_Count, (NSUInteger)1); + message.strToStr = + [NSMutableDictionary dictionaryWithObjectsAndKeys:@"bar", @"key1", @"baz", @"key2", nil]; + XCTAssertEqual(message.strToStr_Count, (NSUInteger)2); + message.strToStr = + [NSMutableDictionary dictionaryWithObject:@"baz" forKey:@"mumble"]; + XCTAssertEqual(message.strToStr_Count, (NSUInteger)1); +} + - (void)testReplaceAutocreatedMap { // Replacing map should orphan the old one and cause its creator to become // visible.