Merge pull request #3714 from thomasvl/objc_increase_test_coverage

Objc increase test coverage
diff --git a/objectivec/GPBDescriptor.m b/objectivec/GPBDescriptor.m
index 0753a94..3c3844d 100644
--- a/objectivec/GPBDescriptor.m
+++ b/objectivec/GPBDescriptor.m
@@ -1027,10 +1027,6 @@
   return GPBExtensionIsRepeated(description_);
 }
 
-- (BOOL)isMap {
-  return (description_->options & GPBFieldMapKeyMask) != 0;
-}
-
 - (BOOL)isPackable {
   return GPBExtensionIsPacked(description_);
 }
diff --git a/objectivec/GPBUnknownField.m b/objectivec/GPBUnknownField.m
index 15e0a6d..9d5c97f 100644
--- a/objectivec/GPBUnknownField.m
+++ b/objectivec/GPBUnknownField.m
@@ -97,6 +97,7 @@
   if (self == object) return YES;
   if (![object isKindOfClass:[GPBUnknownField class]]) return NO;
   GPBUnknownField *field = (GPBUnknownField *)object;
+  if (number_ != field->number_) return NO;
   BOOL equalVarint =
       (mutableVarintList_.count == 0 && field->mutableVarintList_.count == 0) ||
       [mutableVarintList_ isEqual:field->mutableVarintList_];
@@ -202,8 +203,9 @@
 }
 
 - (NSString *)description {
-  NSMutableString *description = [NSMutableString
-      stringWithFormat:@"<%@ %p>: Field: %d {\n", [self class], self, number_];
+  NSMutableString *description =
+      [NSMutableString stringWithFormat:@"<%@ %p>: Field: %d {\n",
+       [self class], self, number_];
   [mutableVarintList_
       enumerateValuesWithBlock:^(uint64_t value, NSUInteger idx, BOOL *stop) {
 #pragma unused(idx, stop)
diff --git a/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj b/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj
index cd7fcc9..266b05a 100644
--- a/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj
+++ b/objectivec/ProtocolBuffers_OSX.xcodeproj/project.pbxproj
@@ -562,7 +562,7 @@
 			attributes = {
 				LastSwiftUpdateCheck = 0710;
 				LastTestingUpgradeCheck = 0600;
-				LastUpgradeCheck = 0800;
+				LastUpgradeCheck = 0900;
 				TargetAttributes = {
 					8BBEA4A5147C727100C4ADB7 = {
 						LastSwiftMigration = 0800;
@@ -794,6 +794,8 @@
 				CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
 				CLANG_STATIC_ANALYZER_MODE = deep;
 				CLANG_WARN_ASSIGN_ENUM = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_EMPTY_BODY = YES;
@@ -803,6 +805,8 @@
 				CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
@@ -860,6 +864,8 @@
 				CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
 				CLANG_STATIC_ANALYZER_MODE = deep;
 				CLANG_WARN_ASSIGN_ENUM = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_EMPTY_BODY = YES;
@@ -869,6 +875,8 @@
 				CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
diff --git a/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme b/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
index 2883109..d3c2938 100644
--- a/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
+++ b/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0800"
+   LastUpgradeVersion = "0900"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
       buildConfiguration = "Release"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
          <TestableReference
@@ -300,6 +301,7 @@
       buildConfiguration = "Release"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
diff --git a/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme b/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
index b1243b7..e7fb821 100644
--- a/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
+++ b/objectivec/ProtocolBuffers_OSX.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0800"
+   LastUpgradeVersion = "0900"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -54,6 +54,7 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       shouldUseLaunchSchemeArgsEnv = "YES"
       codeCoverageEnabled = "YES">
       <Testables>
@@ -89,6 +90,7 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
diff --git a/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj b/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj
index 2211cb3..1f1172b 100644
--- a/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj
+++ b/objectivec/ProtocolBuffers_iOS.xcodeproj/project.pbxproj
@@ -639,7 +639,7 @@
 			attributes = {
 				LastSwiftUpdateCheck = 0710;
 				LastTestingUpgradeCheck = 0600;
-				LastUpgradeCheck = 0800;
+				LastUpgradeCheck = 0900;
 				TargetAttributes = {
 					8BBEA4A5147C727100C4ADB7 = {
 						LastSwiftMigration = 0800;
@@ -956,6 +956,8 @@
 				CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
 				CLANG_STATIC_ANALYZER_MODE = deep;
 				CLANG_WARN_ASSIGN_ENUM = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_EMPTY_BODY = YES;
@@ -965,6 +967,8 @@
 				CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
@@ -1023,6 +1027,8 @@
 				CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES;
 				CLANG_STATIC_ANALYZER_MODE = deep;
 				CLANG_WARN_ASSIGN_ENUM = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_COMMA = YES;
 				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
 				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
 				CLANG_WARN_EMPTY_BODY = YES;
@@ -1032,6 +1038,8 @@
 				CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES;
 				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
 				CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
 				CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
 				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE;
diff --git a/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme b/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
index 1ba3a32..03acb08 100644
--- a/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
+++ b/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/PerformanceTests.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0800"
+   LastUpgradeVersion = "0900"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -26,6 +26,7 @@
       buildConfiguration = "Release"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       shouldUseLaunchSchemeArgsEnv = "YES">
       <Testables>
          <TestableReference
@@ -309,6 +310,7 @@
       buildConfiguration = "Release"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
diff --git a/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme b/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
index edbe689..98bcd56 100644
--- a/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
+++ b/objectivec/ProtocolBuffers_iOS.xcodeproj/xcshareddata/xcschemes/ProtocolBuffers.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "0800"
+   LastUpgradeVersion = "0900"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
@@ -54,6 +54,7 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       shouldUseLaunchSchemeArgsEnv = "YES"
       codeCoverageEnabled = "YES">
       <Testables>
@@ -89,6 +90,7 @@
       buildConfiguration = "Debug"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      language = ""
       launchStyle = "0"
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
diff --git a/objectivec/Tests/GPBArrayTests.m b/objectivec/Tests/GPBArrayTests.m
index 31f7550..e414d90 100644
--- a/objectivec/Tests/GPBArrayTests.m
+++ b/objectivec/Tests/GPBArrayTests.m
@@ -39,6 +39,7 @@
 // To let the testing macros work, add some extra methods to simplify things.
 @interface GPBEnumArray (TestingTweak)
 + (instancetype)arrayWithValue:(int32_t)value;
++ (instancetype)arrayWithCapacity:(NSUInteger)count;
 - (instancetype)initWithValues:(const int32_t [])values
                          count:(NSUInteger)count;
 @end
@@ -72,6 +73,10 @@
                                          rawValues:&value
                                              count:1] autorelease];
 }
++ (instancetype)arrayWithCapacity:(NSUInteger)count {
+  return [[[self alloc] initWithValidationFunction:TestingEnum_IsValidValue
+                                          capacity:count] autorelease];
+}
 - (instancetype)initWithValues:(const int32_t [])values
                          count:(NSUInteger)count {
   return [self initWithValidationFunction:TestingEnum_IsValidValue
@@ -177,6 +182,8 @@
 //%    XCTAssertNotEqual(idx, 0U);
 //%    ++idx2;
 //%  }];
+//%  // Ensure description doesn't choke.
+//%  XCTAssertTrue(array.description.length > 10);
 //%  [array release];
 //%}
 //%
@@ -201,6 +208,10 @@
 //%            NAME$S                     count:GPBARRAYSIZE(kValues3)];
 //%  XCTAssertNotNil(array3);
 //%
+//%  // Identity
+//%  XCTAssertTrue([array1 isEqual:array1]);
+//%  // Wrong type doesn't blow up.
+//%  XCTAssertFalse([array1 isEqual:@"bogus"]);
 //%  // 1/1Prime should be different objects, but equal.
 //%  XCTAssertNotEqual(array1, array1prime);
 //%  XCTAssertEqualObjects(array1, array1prime);
@@ -271,6 +282,12 @@
 //%  [array add##HELPER##ValuesFromArray:array2];
 //%  XCTAssertEqual(array.count, 5U);
 //%
+//%  // Zero/nil inputs do nothing.
+//%  [array addValues:kValues1 count:0];
+//%  XCTAssertEqual(array.count, 5U);
+//%  [array addValues:NULL count:5];
+//%  XCTAssertEqual(array.count, 5U);
+//%
 //%  XCTAssertEqual([array valueAtIndex:0], VAL1);
 //%  XCTAssertEqual([array valueAtIndex:1], VAL2);
 //%  XCTAssertEqual([array valueAtIndex:2], VAL3);
@@ -391,9 +408,9 @@
 //%- (void)testInternalResizing {
 //%  const TYPE kValues[] = { VAL1, VAL2, VAL3, VAL4 };
 //%  GPB##NAME##Array *array =
-//%      [[GPB##NAME##Array alloc] initWithValues:kValues
-//%            NAME$S                     count:GPBARRAYSIZE(kValues)];
+//%      [GPB##NAME##Array arrayWithCapacity:GPBARRAYSIZE(kValues)];
 //%  XCTAssertNotNil(array);
+//%  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 //%
 //%  // Add/remove to trigger the intneral buffer to grow/shrink.
 //%  for (int i = 0; i < 100; ++i) {
@@ -410,7 +427,6 @@
 //%  XCTAssertEqual(array.count, 404U);
 //%  [array removeAll];
 //%  XCTAssertEqual(array.count, 0U);
-//%  [array release];
 //%}
 //%
 //%@end
@@ -510,6 +526,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -534,6 +552,10 @@
                                       count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -604,6 +626,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 1);
   XCTAssertEqual([array valueAtIndex:1], 2);
   XCTAssertEqual([array valueAtIndex:2], 3);
@@ -724,9 +752,9 @@
 - (void)testInternalResizing {
   const int32_t kValues[] = { 1, 2, 3, 4 };
   GPBInt32Array *array =
-      [[GPBInt32Array alloc] initWithValues:kValues
-                                      count:GPBARRAYSIZE(kValues)];
+      [GPBInt32Array arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -743,7 +771,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -843,6 +870,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -867,6 +896,10 @@
                                        count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -937,6 +970,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 11U);
   XCTAssertEqual([array valueAtIndex:1], 12U);
   XCTAssertEqual([array valueAtIndex:2], 13U);
@@ -1057,9 +1096,9 @@
 - (void)testInternalResizing {
   const uint32_t kValues[] = { 11U, 12U, 13U, 14U };
   GPBUInt32Array *array =
-      [[GPBUInt32Array alloc] initWithValues:kValues
-                                       count:GPBARRAYSIZE(kValues)];
+      [GPBUInt32Array arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -1076,7 +1115,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -1176,6 +1214,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -1200,6 +1240,10 @@
                                       count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -1270,6 +1314,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 31LL);
   XCTAssertEqual([array valueAtIndex:1], 32LL);
   XCTAssertEqual([array valueAtIndex:2], 33LL);
@@ -1390,9 +1440,9 @@
 - (void)testInternalResizing {
   const int64_t kValues[] = { 31LL, 32LL, 33LL, 34LL };
   GPBInt64Array *array =
-      [[GPBInt64Array alloc] initWithValues:kValues
-                                      count:GPBARRAYSIZE(kValues)];
+      [GPBInt64Array arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -1409,7 +1459,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -1509,6 +1558,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -1533,6 +1584,10 @@
                                        count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -1603,6 +1658,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 41ULL);
   XCTAssertEqual([array valueAtIndex:1], 42ULL);
   XCTAssertEqual([array valueAtIndex:2], 43ULL);
@@ -1723,9 +1784,9 @@
 - (void)testInternalResizing {
   const uint64_t kValues[] = { 41ULL, 42ULL, 43ULL, 44ULL };
   GPBUInt64Array *array =
-      [[GPBUInt64Array alloc] initWithValues:kValues
-                                       count:GPBARRAYSIZE(kValues)];
+      [GPBUInt64Array arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -1742,7 +1803,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -1842,6 +1902,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -1866,6 +1928,10 @@
                                       count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -1936,6 +2002,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 51.f);
   XCTAssertEqual([array valueAtIndex:1], 52.f);
   XCTAssertEqual([array valueAtIndex:2], 53.f);
@@ -2056,9 +2128,9 @@
 - (void)testInternalResizing {
   const float kValues[] = { 51.f, 52.f, 53.f, 54.f };
   GPBFloatArray *array =
-      [[GPBFloatArray alloc] initWithValues:kValues
-                                      count:GPBARRAYSIZE(kValues)];
+      [GPBFloatArray arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -2075,7 +2147,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -2175,6 +2246,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -2199,6 +2272,10 @@
                                        count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -2269,6 +2346,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 61.);
   XCTAssertEqual([array valueAtIndex:1], 62.);
   XCTAssertEqual([array valueAtIndex:2], 63.);
@@ -2389,9 +2472,9 @@
 - (void)testInternalResizing {
   const double kValues[] = { 61., 62., 63., 64. };
   GPBDoubleArray *array =
-      [[GPBDoubleArray alloc] initWithValues:kValues
-                                       count:GPBARRAYSIZE(kValues)];
+      [GPBDoubleArray arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -2408,7 +2491,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -2508,6 +2590,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -2532,6 +2616,10 @@
                                      count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -2602,6 +2690,12 @@
   [array addValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], TRUE);
   XCTAssertEqual([array valueAtIndex:1], TRUE);
   XCTAssertEqual([array valueAtIndex:2], FALSE);
@@ -2722,9 +2816,9 @@
 - (void)testInternalResizing {
   const BOOL kValues[] = { TRUE, TRUE, FALSE, FALSE };
   GPBBoolArray *array =
-      [[GPBBoolArray alloc] initWithValues:kValues
-                                     count:GPBARRAYSIZE(kValues)];
+      [GPBBoolArray arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -2741,7 +2835,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
@@ -2841,6 +2934,8 @@
     XCTAssertNotEqual(idx, 0U);
     ++idx2;
   }];
+  // Ensure description doesn't choke.
+  XCTAssertTrue(array.description.length > 10);
   [array release];
 }
 
@@ -2865,6 +2960,10 @@
                                      count:GPBARRAYSIZE(kValues3)];
   XCTAssertNotNil(array3);
 
+  // Identity
+  XCTAssertTrue([array1 isEqual:array1]);
+  // Wrong type doesn't blow up.
+  XCTAssertFalse([array1 isEqual:@"bogus"]);
   // 1/1Prime should be different objects, but equal.
   XCTAssertNotEqual(array1, array1prime);
   XCTAssertEqualObjects(array1, array1prime);
@@ -2935,6 +3034,12 @@
   [array addRawValuesFromArray:array2];
   XCTAssertEqual(array.count, 5U);
 
+  // Zero/nil inputs do nothing.
+  [array addValues:kValues1 count:0];
+  XCTAssertEqual(array.count, 5U);
+  [array addValues:NULL count:5];
+  XCTAssertEqual(array.count, 5U);
+
   XCTAssertEqual([array valueAtIndex:0], 71);
   XCTAssertEqual([array valueAtIndex:1], 72);
   XCTAssertEqual([array valueAtIndex:2], 73);
@@ -3055,9 +3160,9 @@
 - (void)testInternalResizing {
   const int32_t kValues[] = { 71, 72, 73, 74 };
   GPBEnumArray *array =
-      [[GPBEnumArray alloc] initWithValues:kValues
-                                     count:GPBARRAYSIZE(kValues)];
+      [GPBEnumArray arrayWithCapacity:GPBARRAYSIZE(kValues)];
   XCTAssertNotNil(array);
+  [array addValues:kValues count:GPBARRAYSIZE(kValues)];
 
   // Add/remove to trigger the intneral buffer to grow/shrink.
   for (int i = 0; i < 100; ++i) {
@@ -3074,7 +3179,6 @@
   XCTAssertEqual(array.count, 404U);
   [array removeAll];
   XCTAssertEqual(array.count, 0U);
-  [array release];
 }
 
 @end
diff --git a/objectivec/Tests/GPBDescriptorTests.m b/objectivec/Tests/GPBDescriptorTests.m
index 1e1c3de..199ea65 100644
--- a/objectivec/Tests/GPBDescriptorTests.m
+++ b/objectivec/Tests/GPBDescriptorTests.m
@@ -32,7 +32,7 @@
 
 #import <objc/runtime.h>
 
-#import "GPBDescriptor.h"
+#import "GPBDescriptor_PackagePrivate.h"
 #import "google/protobuf/Unittest.pbobjc.h"
 #import "google/protobuf/UnittestObjc.pbobjc.h"
 #import "google/protobuf/Descriptor.pbobjc.h"
@@ -83,6 +83,8 @@
   XCTAssertNotNil(fieldDescriptorWithNumber.enumDescriptor);
   XCTAssertEqualObjects(fieldDescriptorWithNumber.enumDescriptor.name,
                         @"TestAllTypes_NestedEnum");
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeEnum);
 
   // Foreign Enum
   fieldDescriptorWithName = [descriptor fieldWithName:@"optionalForeignEnum"];
@@ -93,6 +95,8 @@
   XCTAssertNotNil(fieldDescriptorWithNumber.enumDescriptor);
   XCTAssertEqualObjects(fieldDescriptorWithNumber.enumDescriptor.name,
                         @"ForeignEnum");
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeEnum);
 
   // Import Enum
   fieldDescriptorWithName = [descriptor fieldWithName:@"optionalImportEnum"];
@@ -103,6 +107,8 @@
   XCTAssertNotNil(fieldDescriptorWithNumber.enumDescriptor);
   XCTAssertEqualObjects(fieldDescriptorWithNumber.enumDescriptor.name,
                         @"ImportEnum");
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeEnum);
 
   // Nested Message
   fieldDescriptorWithName = [descriptor fieldWithName:@"optionalNestedMessage"];
@@ -111,6 +117,8 @@
   XCTAssertNotNil(fieldDescriptorWithNumber);
   XCTAssertEqual(fieldDescriptorWithName, fieldDescriptorWithNumber);
   XCTAssertNil(fieldDescriptorWithNumber.enumDescriptor);
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeMessage);
 
   // Foreign Message
   fieldDescriptorWithName =
@@ -120,6 +128,8 @@
   XCTAssertNotNil(fieldDescriptorWithNumber);
   XCTAssertEqual(fieldDescriptorWithName, fieldDescriptorWithNumber);
   XCTAssertNil(fieldDescriptorWithNumber.enumDescriptor);
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeMessage);
 
   // Import Message
   fieldDescriptorWithName = [descriptor fieldWithName:@"optionalImportMessage"];
@@ -128,6 +138,12 @@
   XCTAssertNotNil(fieldDescriptorWithNumber);
   XCTAssertEqual(fieldDescriptorWithName, fieldDescriptorWithNumber);
   XCTAssertNil(fieldDescriptorWithNumber.enumDescriptor);
+  XCTAssertEqual(fieldDescriptorWithName.number, fieldDescriptorWithNumber.number);
+  XCTAssertEqual(fieldDescriptorWithName.dataType, GPBDataTypeMessage);
+
+  // Some failed lookups.
+  XCTAssertNil([descriptor fieldWithName:@"NOT THERE"]);
+  XCTAssertNil([descriptor fieldWithNumber:9876543]);
 }
 
 - (void)testEnumDescriptor {
@@ -159,6 +175,7 @@
   XCTAssertNotNil(enumName);
   XCTAssertTrue([descriptor getValue:&value forEnumTextFormatName:@"FOO"]);
   XCTAssertEqual(value, TestAllTypes_NestedEnum_Foo);
+  XCTAssertNil([descriptor textFormatNameForValue:99999]);
 
   // Bad values
   enumName = [descriptor enumNameForValue:0];
@@ -253,4 +270,102 @@
   XCTAssertNil(bazString.containingOneof);
 }
 
+- (void)testExtensiondDescriptor {
+  Class msgClass = [TestAllExtensions class];
+  Class packedMsgClass = [TestPackedExtensions class];
+
+  // Int
+
+  GPBExtensionDescriptor *descriptor = [UnittestRoot optionalInt32Extension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertEqualObjects(descriptor.defaultValue, @0);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  descriptor = [UnittestRoot defaultInt32Extension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertEqualObjects(descriptor.defaultValue, @41);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  // Enum
+
+  descriptor = [UnittestRoot optionalNestedEnumExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertEqual(descriptor.defaultValue, @1);
+  XCTAssertEqualObjects(descriptor.enumDescriptor.name, @"TestAllTypes_NestedEnum");
+
+  descriptor = [UnittestRoot defaultNestedEnumExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertEqual(descriptor.defaultValue, @2);
+  XCTAssertEqualObjects(descriptor.enumDescriptor.name, @"TestAllTypes_NestedEnum");
+
+  // Message
+
+  descriptor = [UnittestRoot optionalNestedMessageExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  // Repeated Int
+
+  descriptor = [UnittestRoot repeatedInt32Extension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  descriptor = [UnittestRoot packedInt32Extension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, packedMsgClass);  // ptr equality
+  XCTAssertTrue(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  // Repeated Enum
+
+  descriptor = [UnittestRoot repeatedNestedEnumExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertEqualObjects(descriptor.enumDescriptor.name, @"TestAllTypes_NestedEnum");
+
+  descriptor = [UnittestRoot packedEnumExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, packedMsgClass);  // ptr equality
+  XCTAssertTrue(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertEqualObjects(descriptor.enumDescriptor.name, @"ForeignEnum");
+
+  // Repeated Message
+
+  descriptor = [UnittestRoot repeatedNestedMessageExtension];
+  XCTAssertNotNil(descriptor);
+  XCTAssertEqual(descriptor.containingMessageClass, msgClass);  // ptr equality
+  XCTAssertFalse(descriptor.isPackable);
+  XCTAssertNil(descriptor.defaultValue);
+  XCTAssertNil(descriptor.enumDescriptor);
+
+  // Compare (used internally for serialization).
+
+  GPBExtensionDescriptor *ext1 = [UnittestRoot optionalInt32Extension];
+  XCTAssertEqual(ext1.fieldNumber, 1u);
+  GPBExtensionDescriptor *ext2 = [UnittestRoot optionalInt64Extension];
+  XCTAssertEqual(ext2.fieldNumber, 2u);
+
+  XCTAssertEqual([ext1 compareByFieldNumber:ext2], NSOrderedAscending);
+  XCTAssertEqual([ext2 compareByFieldNumber:ext1], NSOrderedDescending);
+  XCTAssertEqual([ext1 compareByFieldNumber:ext1], NSOrderedSame);
+}
+
 @end
diff --git a/objectivec/Tests/GPBUnknownFieldSetTest.m b/objectivec/Tests/GPBUnknownFieldSetTest.m
index 5a07ecc..64cbd2d 100644
--- a/objectivec/Tests/GPBUnknownFieldSetTest.m
+++ b/objectivec/Tests/GPBUnknownFieldSetTest.m
@@ -64,6 +64,95 @@
   unknownFields_ = emptyMessage_.unknownFields;
 }
 
+- (void)testInvalidFieldNumber {
+  GPBUnknownFieldSet *set = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* field = [[[GPBUnknownField alloc] initWithNumber:0] autorelease];
+  XCTAssertThrowsSpecificNamed([set addField:field], NSException, NSInvalidArgumentException);
+}
+
+- (void)testEqualityAndHash {
+  // Empty
+
+  GPBUnknownFieldSet *set1 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  XCTAssertTrue([set1 isEqual:set1]);
+  XCTAssertFalse([set1 isEqual:@"foo"]);
+  GPBUnknownFieldSet *set2 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // Varint
+
+  GPBUnknownField* field1 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
+  [field1 addVarint:1];
+  [set1 addField:field1];
+  XCTAssertNotEqualObjects(set1, set2);
+  GPBUnknownField* field2 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
+  [field2 addVarint:1];
+  [set2 addField:field2];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // Fixed32
+
+  field1 = [[[GPBUnknownField alloc] initWithNumber:2] autorelease];
+  [field1 addFixed32:2];
+  [set1 addField:field1];
+  XCTAssertNotEqualObjects(set1, set2);
+  field2 = [[[GPBUnknownField alloc] initWithNumber:2] autorelease];
+  [field2 addFixed32:2];
+  [set2 addField:field2];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // Fixed64
+
+  field1 = [[[GPBUnknownField alloc] initWithNumber:3] autorelease];
+  [field1 addFixed64:3];
+  [set1 addField:field1];
+  XCTAssertNotEqualObjects(set1, set2);
+  field2 = [[[GPBUnknownField alloc] initWithNumber:3] autorelease];
+  [field2 addFixed64:3];
+  [set2 addField:field2];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // LengthDelimited
+
+  field1 = [[[GPBUnknownField alloc] initWithNumber:4] autorelease];
+  [field1 addLengthDelimited:DataFromCStr("foo")];
+  [set1 addField:field1];
+  XCTAssertNotEqualObjects(set1, set2);
+  field2 = [[[GPBUnknownField alloc] initWithNumber:4] autorelease];
+  [field2 addLengthDelimited:DataFromCStr("foo")];
+  [set2 addField:field2];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // Group
+
+  GPBUnknownFieldSet *group1 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup1 = [[[GPBUnknownField alloc] initWithNumber:10] autorelease];
+  [fieldGroup1 addVarint:1];
+  [group1 addField:fieldGroup1];
+  GPBUnknownFieldSet *group2 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup2 = [[[GPBUnknownField alloc] initWithNumber:10] autorelease];
+  [fieldGroup2 addVarint:1];
+  [group2 addField:fieldGroup2];
+
+  field1 = [[[GPBUnknownField alloc] initWithNumber:5] autorelease];
+  [field1 addGroup:group1];
+  [set1 addField:field1];
+  XCTAssertNotEqualObjects(set1, set2);
+  field2 = [[[GPBUnknownField alloc] initWithNumber:5] autorelease];
+  [field2 addGroup:group2];
+  [set2 addField:field2];
+  XCTAssertEqualObjects(set1, set2);
+  XCTAssertEqual([set1 hash], [set2 hash]);
+
+  // Exercise description for completeness.
+  XCTAssertTrue(set1.description.length > 10);
+}
+
 // Constructs a protocol buffer which contains fields with all the same
 // numbers as allFieldsData except that each field is some other wire
 // type.
@@ -116,10 +205,25 @@
   field = [[[GPBUnknownField alloc] initWithNumber:3] autorelease];
   [field addVarint:4];
   [set1 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:4] autorelease];
+  [field addFixed32:6];
+  [set1 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:5] autorelease];
+  [field addFixed64:20];
+  [set1 addField:field];
   field = [[[GPBUnknownField alloc] initWithNumber:10] autorelease];
   [field addLengthDelimited:DataFromCStr("data1")];
   [set1 addField:field];
 
+  GPBUnknownFieldSet *group1 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup1 = [[[GPBUnknownField alloc] initWithNumber:200] autorelease];
+  [fieldGroup1 addVarint:100];
+  [group1 addField:fieldGroup1];
+
+  field = [[[GPBUnknownField alloc] initWithNumber:11] autorelease];
+  [field addGroup:group1];
+  [set1 addField:field];
+
   GPBUnknownFieldSet* set2 = [[[GPBUnknownFieldSet alloc] init] autorelease];
   field = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
   [field addVarint:1];
@@ -127,10 +231,25 @@
   field = [[[GPBUnknownField alloc] initWithNumber:3] autorelease];
   [field addVarint:3];
   [set2 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:4] autorelease];
+  [field addFixed32:7];
+  [set2 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:5] autorelease];
+  [field addFixed64:30];
+  [set2 addField:field];
   field = [[[GPBUnknownField alloc] initWithNumber:10] autorelease];
   [field addLengthDelimited:DataFromCStr("data2")];
   [set2 addField:field];
 
+  GPBUnknownFieldSet *group2 = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup2 = [[[GPBUnknownField alloc] initWithNumber:201] autorelease];
+  [fieldGroup2 addVarint:99];
+  [group2 addField:fieldGroup2];
+
+  field = [[[GPBUnknownField alloc] initWithNumber:11] autorelease];
+  [field addGroup:group2];
+  [set2 addField:field];
+
   GPBUnknownFieldSet* set3 = [[[GPBUnknownFieldSet alloc] init] autorelease];
   field = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
   [field addVarint:1];
@@ -143,11 +262,33 @@
   [set3 addField:field];
   [field addVarint:3];
   [set3 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:4] autorelease];
+  [field addFixed32:6];
+  [field addFixed32:7];
+  [set3 addField:field];
+  field = [[[GPBUnknownField alloc] initWithNumber:5] autorelease];
+  [field addFixed64:20];
+  [field addFixed64:30];
+  [set3 addField:field];
   field = [[[GPBUnknownField alloc] initWithNumber:10] autorelease];
   [field addLengthDelimited:DataFromCStr("data1")];
   [field addLengthDelimited:DataFromCStr("data2")];
   [set3 addField:field];
 
+  GPBUnknownFieldSet *group3a = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup3a1 = [[[GPBUnknownField alloc] initWithNumber:200] autorelease];
+  [fieldGroup3a1 addVarint:100];
+  [group3a addField:fieldGroup3a1];
+  GPBUnknownFieldSet *group3b = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup3b2 = [[[GPBUnknownField alloc] initWithNumber:201] autorelease];
+  [fieldGroup3b2 addVarint:99];
+  [group3b addField:fieldGroup3b2];
+
+  field = [[[GPBUnknownField alloc] initWithNumber:11] autorelease];
+  [field addGroup:group1];
+  [field addGroup:group3b];
+  [set3 addField:field];
+
   TestEmptyMessage* source1 = [TestEmptyMessage message];
   [source1 setUnknownFields:set1];
   TestEmptyMessage* source2 = [TestEmptyMessage message];
@@ -250,6 +391,107 @@
   XCTAssertEqual(0x7FFFFFFFFFFFFFFFULL, [field2.varintList valueAtIndex:0]);
 }
 
+#pragma mark - Field tests
+// Some tests directly on fields since the dictionary in FieldSet can gate
+// testing some of these.
+
+- (void)testFieldEqualityAndHash {
+  GPBUnknownField* field1 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
+  XCTAssertTrue([field1 isEqual:field1]);
+  XCTAssertFalse([field1 isEqual:@"foo"]);
+  GPBUnknownField* field2 = [[[GPBUnknownField alloc] initWithNumber:2] autorelease];
+  XCTAssertNotEqualObjects(field1, field2);
+
+  field2 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // Varint
+
+  [field1 addVarint:10];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addVarint:10];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+  [field1 addVarint:11];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addVarint:11];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // Fixed32
+
+  [field1 addFixed32:20];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addFixed32:20];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+  [field1 addFixed32:21];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addFixed32:21];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // Fixed64
+
+  [field1 addFixed64:30];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addFixed64:30];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+  [field1 addFixed64:31];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addFixed64:31];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // LengthDelimited
+
+  [field1 addLengthDelimited:DataFromCStr("foo")];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addLengthDelimited:DataFromCStr("foo")];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+  [field1 addLengthDelimited:DataFromCStr("bar")];
+  XCTAssertNotEqualObjects(field1, field2);
+  [field2 addLengthDelimited:DataFromCStr("bar")];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // Group
+
+  GPBUnknownFieldSet *group = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  GPBUnknownField* fieldGroup = [[[GPBUnknownField alloc] initWithNumber:100] autorelease];
+  [fieldGroup addVarint:100];
+  [group addField:fieldGroup];
+  [field1 addGroup:group];
+  XCTAssertNotEqualObjects(field1, field2);
+  group = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  fieldGroup = [[[GPBUnknownField alloc] initWithNumber:100] autorelease];
+  [fieldGroup addVarint:100];
+  [group addField:fieldGroup];
+  [field2 addGroup:group];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  group = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  fieldGroup = [[[GPBUnknownField alloc] initWithNumber:101] autorelease];
+  [fieldGroup addVarint:101];
+  [group addField:fieldGroup];
+  [field1 addGroup:group];
+  XCTAssertNotEqualObjects(field1, field2);
+  group = [[[GPBUnknownFieldSet alloc] init] autorelease];
+  fieldGroup = [[[GPBUnknownField alloc] initWithNumber:101] autorelease];
+  [fieldGroup addVarint:101];
+  [group addField:fieldGroup];
+  [field2 addGroup:group];
+  XCTAssertEqualObjects(field1, field2);
+  XCTAssertEqual([field1 hash], [field2 hash]);
+
+  // Exercise description for completeness.
+  XCTAssertTrue(field1.description.length > 10);
+}
+
 - (void)testMergingFields {
   GPBUnknownField* field1 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
   [field1 addVarint:1];
@@ -257,9 +499,8 @@
   [field1 addFixed64:3];
   [field1 addLengthDelimited:[NSData dataWithBytes:"hello" length:5]];
   [field1 addGroup:[[unknownFields_ copy] autorelease]];
-  GPBUnknownField* field2 = [[[GPBUnknownField alloc] initWithNumber:2] autorelease];
+  GPBUnknownField* field2 = [[[GPBUnknownField alloc] initWithNumber:1] autorelease];
   [field2 mergeFromField:field1];
-  XCTAssertEqualObjects(field1, field2);
 }
 
 @end