diff --git a/AppCenter.podspec b/AppCenter.podspec index 6aab49e7b2..655eee55f6 100644 --- a/AppCenter.podspec +++ b/AppCenter.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'AppCenter' - s.version = '1.10.1' + s.version = '1.11.0' s.summary = 'Visual Studio App Center is your continuous integration, delivery and learning solution for iOS and macOS apps.' s.description = <<-DESC diff --git a/AppCenter/AppCenter.xcodeproj/project.pbxproj b/AppCenter/AppCenter.xcodeproj/project.pbxproj index 664a61131d..e46765d8e6 100644 --- a/AppCenter/AppCenter.xcodeproj/project.pbxproj +++ b/AppCenter/AppCenter.xcodeproj/project.pbxproj @@ -148,7 +148,7 @@ 0446DF091F3B864600C8E338 /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */; }; 0446DF0A1F3B864600C8E338 /* MSChannelUnitDefaultTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6EB1F40D1D2443B7005F9F99 /* MSChannelUnitDefaultTests.m */; }; 0446DF0B1F3B864600C8E338 /* MSAppCenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 384959D41D491D4F008F6B3A /* MSAppCenterTests.m */; }; - 0446DF0C1F3B864600C8E338 /* MSStoragePerfomanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */; }; + 0446DF0C1F3B864600C8E338 /* MSStoragePerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerformanceTests.m */; }; 0446DF0D1F3B864600C8E338 /* MSLogDBStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C7877911EA0CFF3002263CC /* MSLogDBStorageTests.m */; }; 0446DF0E1F3B864600C8E338 /* MSHttpTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 386E8D911E25932100EECF0F /* MSHttpTestUtil.m */; }; 0446DF0F1F3B864600C8E338 /* MSDeviceHistoryInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */; }; @@ -169,7 +169,7 @@ 0446DF2E1F3B86C500C8E338 /* MSAbstractLogInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3844FF121E8C22CC003E9194 /* MSAbstractLogInternal.h */; }; 0446DF2F1F3B86CB00C8E338 /* MSWrapperSdkInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD745D1F22BBBB0070E7DF /* MSWrapperSdkInternal.h */; }; 0446DF301F3B86CB00C8E338 /* MSWrapperSdkInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = B2CD745D1F22BBBB0070E7DF /* MSWrapperSdkInternal.h */; }; - 0446DF311F3B86FE00C8E338 /* MSStoragePerfomanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */; }; + 0446DF311F3B86FE00C8E338 /* MSStoragePerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerformanceTests.m */; }; 0446DF321F3B870700C8E338 /* MSDBStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3849BA7D1EF3489D0072E3E0 /* MSDBStorageTests.m */; }; 0446DF331F3B870A00C8E338 /* MSLogDBStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C7877911EA0CFF3002263CC /* MSLogDBStorageTests.m */; }; 0446DF341F3B871B00C8E338 /* OCHamcrest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0484DD121F3908840092B777 /* OCHamcrest.framework */; }; @@ -184,7 +184,6 @@ 045BC3191E3FD8AC00B6C960 /* MSKeychainUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 045BC3181E3FD8AC00B6C960 /* MSKeychainUtil.h */; }; 045C0BEE1EEB122D0038FD6B /* MSAppDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C4471E1ECE5352002E1B11 /* MSAppDelegateForwarder.h */; }; 045C0BEF1EEB12300038FD6B /* MSAppDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */; }; - 045C0BF01EEB12320038FD6B /* MSAppDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */; }; 045C85101FE0B1D900089540 /* MSSessionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 045C850F1FE0B1D900089540 /* MSSessionContext.h */; }; 045C85111FE0B2D200089540 /* MSSessionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 045C850F1FE0B1D900089540 /* MSSessionContext.h */; }; 045C85121FE0B2D300089540 /* MSSessionContext.h in Headers */ = {isa = PBXBuildFile; fileRef = 045C850F1FE0B1D900089540 /* MSSessionContext.h */; }; @@ -275,7 +274,6 @@ 047EBBA21FE3086D009BB1C8 /* MSSessionHistoryInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 047EBB9C1FE30842009BB1C8 /* MSSessionHistoryInfo.h */; }; 04873E821F2FCE3700A13AFF /* MSAppDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C4471E1ECE5352002E1B11 /* MSAppDelegateForwarder.h */; }; 04873E831F2FCE3E00A13AFF /* MSAppDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */; }; - 04873E841F2FCF9000A13AFF /* MSAppDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */; }; 04873E871F2FDDEC00A13AFF /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38641B041EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m */; }; 049378391FE44539000ADBAF /* MSSessionContextPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 049378381FE44539000ADBAF /* MSSessionContextPrivate.h */; }; 049378411FE4914B000ADBAF /* MSSessionContextTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 0493783F1FE4913C000ADBAF /* MSSessionContextTests.m */; }; @@ -294,6 +292,9 @@ 04A140891ECE63C3001CEE94 /* MSLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B24F3F161D93A3FF00827213 /* MSLoggerTests.m */; }; 04A1408A1ECE63C7001CEE94 /* MSLogContainerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E88EBBFA1D2C8CC7007E7785 /* MSLogContainerTests.m */; }; 04A1408B1ECE63CB001CEE94 /* MSAppCenterIngestionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E829E4221D25C8BA00F19DA1 /* MSAppCenterIngestionTests.m */; }; + 04A20D72217660E10096723C /* MSWrapperLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A20D70217660D50096723C /* MSWrapperLoggerTests.m */; }; + 04A20D73217660E30096723C /* MSWrapperLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A20D70217660D50096723C /* MSWrapperLoggerTests.m */; }; + 04A20D74217660E40096723C /* MSWrapperLoggerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A20D70217660D50096723C /* MSWrapperLoggerTests.m */; }; 04A79A4320C6F432006D0072 /* MSOneCollectorIngestion.h in Headers */ = {isa = PBXBuildFile; fileRef = B2B7D50920C5E562001D31B8 /* MSOneCollectorIngestion.h */; }; 04A79A4420C6F433006D0072 /* MSOneCollectorIngestion.h in Headers */ = {isa = PBXBuildFile; fileRef = B2B7D50920C5E562001D31B8 /* MSOneCollectorIngestion.h */; }; 04A79A4520C6F436006D0072 /* MSOneCollectorIngestion.m in Sources */ = {isa = PBXBuildFile; fileRef = B2B7D50A20C5E562001D31B8 /* MSOneCollectorIngestion.m */; }; @@ -304,6 +305,9 @@ 04AB676320E18A75002828AA /* MSChannelGroupDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 04AB676220E18A74002828AA /* MSChannelGroupDefaultPrivate.h */; }; 04AB676420E18ABB002828AA /* MSChannelGroupDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 04AB676220E18A74002828AA /* MSChannelGroupDefaultPrivate.h */; }; 04AB676520E18ABD002828AA /* MSChannelGroupDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 04AB676220E18A74002828AA /* MSChannelGroupDefaultPrivate.h */; }; + 04B525B92194D44E00FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525B82194D44E00FA37FD /* MSConstants+Flags.h */; }; + 04B525BA2194D45500FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525B82194D44E00FA37FD /* MSConstants+Flags.h */; }; + 04B525BB2194D45600FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525B82194D44E00FA37FD /* MSConstants+Flags.h */; }; 04B7BBEF1E5FAD4D001A0CE1 /* MSIngestionUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04B7BBEE1E5FAD4D001A0CE1 /* MSIngestionUtilTests.m */; }; 04BED7101ECB63A800E20975 /* OCHamcrest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BED70E1ECB63A100E20975 /* OCHamcrest.framework */; }; 04F21A071ECB74200074978D /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04F21A041ECB73EE0074978D /* OHHTTPStubs.framework */; }; @@ -311,17 +315,24 @@ 04F3613E1FE1A95600A5A6D2 /* MSNoAutoAssignSessionIdLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 04F3613C1FE1A92000A5A6D2 /* MSNoAutoAssignSessionIdLog.h */; }; 04F3613F1FE1A95700A5A6D2 /* MSNoAutoAssignSessionIdLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 04F3613C1FE1A92000A5A6D2 /* MSNoAutoAssignSessionIdLog.h */; }; 04FD126B1E4103CC007ABFE7 /* MSKeychainUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FD126A1E4103CC007ABFE7 /* MSKeychainUtilTests.m */; }; + 266EDC94C8B60B195B13BF40 /* MSCustomPropertiesPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 266ED9D2BA796BA0329F4FC7 /* MSCustomPropertiesPrivate.h */; }; 2DA0322920F747CAB5668AAA /* MSChannelUnitDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA030B94CD3D38725671A79 /* MSChannelUnitDefaultPrivate.h */; }; + 2DA033F72EB5B6AA6990DA39 /* MSMetadataExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA038EE68EEE1414A270ADA /* MSMetadataExtension.h */; }; + 2DA03417C2DCD4BF8D03D3EF /* MSMetadataExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA038EE68EEE1414A270ADA /* MSMetadataExtension.h */; }; 2DA034555E2CB013B268C82C /* MSChannelUnitDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA030B94CD3D38725671A79 /* MSChannelUnitDefaultPrivate.h */; }; + 2DA035A646596E34DAD3E440 /* MSMetadataExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA030746930EE3DCB6A21B3 /* MSMetadataExtension.m */; }; 2DA03DF6ADD26CAC19463B5C /* MSChannelUnitDefaultPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA030B94CD3D38725671A79 /* MSChannelUnitDefaultPrivate.h */; }; + 2DA03E86D60D8AC11B345613 /* MSMetadataExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA030746930EE3DCB6A21B3 /* MSMetadataExtension.m */; }; + 2DA03EFBAD1AC21D07F02509 /* MSMetadataExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2DA030746930EE3DCB6A21B3 /* MSMetadataExtension.m */; }; + 2DA03F97D9D7591A1473C5B6 /* MSMetadataExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA038EE68EEE1414A270ADA /* MSMetadataExtension.h */; }; 3542741D2016769C00BE766F /* MSChannelUnitDefault.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741C2012B53B00BE766F /* MSChannelUnitDefault.h */; }; - 3542741E2016769C00BE766F /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3542741E2016769C00BE766F /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; }; 3542741F2016769C00BE766F /* MSChannelUnitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274192012AEFC00BE766F /* MSChannelUnitProtocol.h */; }; 354274202016769C00BE766F /* MSChannelGroupProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741A2012AF0500BE766F /* MSChannelGroupProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 354274212016769C00BE766F /* MSChannelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274162012AE4000BE766F /* MSChannelProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3542742320167DD500BE766F /* MSEnable.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542742220167DA400BE766F /* MSEnable.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3568A7E2201A8A6300F3B860 /* MSChannelUnitDefault.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741C2012B53B00BE766F /* MSChannelUnitDefault.h */; }; - 3568A7E3201A8A6300F3B860 /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 3568A7E3201A8A6300F3B860 /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; }; 3568A7E4201A8A6300F3B860 /* MSChannelUnitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274192012AEFC00BE766F /* MSChannelUnitProtocol.h */; }; 3568A7E5201A8A6300F3B860 /* MSChannelGroupProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741A2012AF0500BE766F /* MSChannelGroupProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 3568A7E6201A8A6300F3B860 /* MSChannelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274162012AE4000BE766F /* MSChannelProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -399,6 +410,18 @@ 35DFC23E2170052D00455589 /* MSTypedPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35DFC21C2170044600455589 /* MSTypedPropertyTests.m */; }; 35DFC23F2170052F00455589 /* MSTypedPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35DFC21C2170044600455589 /* MSTypedPropertyTests.m */; }; 35DFC2402170053100455589 /* MSTypedPropertyTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35DFC21C2170044600455589 /* MSTypedPropertyTests.m */; }; + 38032087217E85A90089772A /* MSDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032085217E85A90089772A /* MSDelegateForwarder.h */; }; + 38032088217E85A90089772A /* MSDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032085217E85A90089772A /* MSDelegateForwarder.h */; }; + 38032089217E85A90089772A /* MSDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032085217E85A90089772A /* MSDelegateForwarder.h */; }; + 3803208A217E85A90089772A /* MSDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38032086217E85A90089772A /* MSDelegateForwarder.m */; }; + 3803208B217E85A90089772A /* MSDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38032086217E85A90089772A /* MSDelegateForwarder.m */; }; + 3803208C217E85A90089772A /* MSDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38032086217E85A90089772A /* MSDelegateForwarder.m */; }; + 3803208E217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3803208D217E8BD40089772A /* MSDelegateForwarderPrivate.h */; }; + 3803208F217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3803208D217E8BD40089772A /* MSDelegateForwarderPrivate.h */; }; + 38032090217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3803208D217E8BD40089772A /* MSDelegateForwarderPrivate.h */; }; + 38032092217E9DC50089772A /* MSCustomDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032091217E9DC50089772A /* MSCustomDelegate.h */; }; + 38032093217E9DC50089772A /* MSCustomDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032091217E9DC50089772A /* MSCustomDelegate.h */; }; + 38032094217E9DC50089772A /* MSCustomDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38032091217E9DC50089772A /* MSCustomDelegate.h */; }; 380A4DCB1DD6908A00E99219 /* MSUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */; }; 38148D8620D07FB70046257E /* MSCompression.h in Headers */ = {isa = PBXBuildFile; fileRef = 38148D8420D07FB70046257E /* MSCompression.h */; }; 38148D8720D07FB70046257E /* MSCompression.m in Sources */ = {isa = PBXBuildFile; fileRef = 38148D8520D07FB70046257E /* MSCompression.m */; }; @@ -425,6 +448,9 @@ 3849BA7C1EF3145A0072E3E0 /* MSDBStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = 3849BA7B1EF3145A0072E3E0 /* MSDBStorage.m */; }; 3849BA7E1EF3489D0072E3E0 /* MSDBStorageTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3849BA7D1EF3489D0072E3E0 /* MSDBStorageTests.m */; }; 3849BA851EF35D830072E3E0 /* MSDBStoragePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3849BA841EF35D830072E3E0 /* MSDBStoragePrivate.h */; }; + 384A92612188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A92602188BE400099BE70 /* MSDelegateForwarderTestUtil.m */; }; + 384A92622188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A92602188BE400099BE70 /* MSDelegateForwarderTestUtil.m */; }; + 384A92632188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A92602188BE400099BE70 /* MSDelegateForwarderTestUtil.m */; }; 385AD9221D95898D008B354A /* MSServiceAbstractProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = 385AD9211D95898D008B354A /* MSServiceAbstractProtected.h */; }; 385E319B20C1B90700BE7271 /* MSACModelConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 385E319920C1B90700BE7271 /* MSACModelConstants.h */; }; 385E319C20C1B90700BE7271 /* MSACModelConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 385E319920C1B90700BE7271 /* MSACModelConstants.h */; }; @@ -450,7 +476,6 @@ 38BC346F20B8CB0C00119C05 /* MSLogConversion.h in Headers */ = {isa = PBXBuildFile; fileRef = 38BC346C20B8CB0C00119C05 /* MSLogConversion.h */; }; 38C447221ECE5352002E1B11 /* MSAppDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C4471E1ECE5352002E1B11 /* MSAppDelegateForwarder.h */; }; 38C447231ECE5352002E1B11 /* MSAppDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */; }; - 38C447241ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */; }; 38C633C71FDF15DD005F40C9 /* MSCustomApplicationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C633C61FDF15DD005F40C9 /* MSCustomApplicationDelegate.h */; }; 38C633C91FDF163D005F40C9 /* MSAppDelegateUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C633C81FDF163D005F40C9 /* MSAppDelegateUtil.h */; }; 38E1B67A1DDE3FDF000EFED1 /* MSAppCenterPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38E1B6791DDE3FDF000EFED1 /* MSAppCenterPrivate.h */; }; @@ -523,7 +548,7 @@ B2787216201AB9D8004AA9A5 /* MSAppDelegateUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C633C81FDF163D005F40C9 /* MSAppDelegateUtil.h */; }; B2787217201AB9D8004AA9A5 /* MSCustomApplicationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C633C61FDF15DD005F40C9 /* MSCustomApplicationDelegate.h */; }; B2787218201AB9D8004AA9A5 /* MSChannelUnitDefault.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741C2012B53B00BE766F /* MSChannelUnitDefault.h */; }; - B2787219201AB9D8004AA9A5 /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B2787219201AB9D8004AA9A5 /* MSChannelDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741B2012B02600BE766F /* MSChannelDelegate.h */; }; B278721A201AB9D8004AA9A5 /* MSChannelUnitProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274192012AEFC00BE766F /* MSChannelUnitProtocol.h */; }; B278721B201AB9D8004AA9A5 /* MSChannelGroupProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3542741A2012AF0500BE766F /* MSChannelGroupProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; B278721C201AB9D8004AA9A5 /* MSChannelProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 354274162012AE4000BE766F /* MSChannelProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -614,7 +639,7 @@ B2FD53651E567BCF0050F909 /* MSDeviceHistoryInfoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */; }; BA68281EA947BB6E7F78354F /* MSAppCenterIngestion.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6824A001520825F18DFC42 /* MSAppCenterIngestion.h */; }; BA682E63DDB55EED56809831 /* MSAppCenterIngestion.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6822A89D38F04D8D95CE83 /* MSAppCenterIngestion.m */; }; - D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */; }; + D36136831E7BB338004AE043 /* MSStoragePerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D36136821E7BB338004AE043 /* MSStoragePerformanceTests.m */; }; D377A30D1E83A05900B2C97A /* MSMockUserDefaults.m in Sources */ = {isa = PBXBuildFile; fileRef = D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */; }; D38023E81E6EFC7C00466558 /* MSStartServiceLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */; }; D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */ = {isa = PBXBuildFile; fileRef = D38023E71E6EFC7C00466558 /* MSStartServiceLog.h */; }; @@ -727,6 +752,9 @@ F803BBF91E8E3989004B1E7A /* MSCustomPropertiesLog.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BBF81E8E3989004B1E7A /* MSCustomPropertiesLog.m */; }; F803BC381E8E6927004B1E7A /* MSCustomPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BC371E8E6927004B1E7A /* MSCustomPropertiesTests.m */; }; F803BC3A1E8E6963004B1E7A /* MSCustomPropertiesLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F803BC391E8E6963004B1E7A /* MSCustomPropertiesLogTests.m */; }; + F82E4C6D217F159A00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C6C217F159A00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C6E217F159A00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C6C217F159A00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C6F217F159A00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C6C217F159A00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -840,14 +868,19 @@ 0493783F1FE4913C000ADBAF /* MSSessionContextTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSSessionContextTests.m; sourceTree = ""; }; 049BC8281ECE3A5200FB6719 /* iOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; 049BC8291ECE3A5A00FB6719 /* macOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; + 04A20D70217660D50096723C /* MSWrapperLoggerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSWrapperLoggerTests.m; sourceTree = ""; }; 04A79A4720C6F635006D0072 /* MSOneCollectorIngestionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSOneCollectorIngestionTests.m; sourceTree = ""; }; 04AB676220E18A74002828AA /* MSChannelGroupDefaultPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSChannelGroupDefaultPrivate.h; sourceTree = ""; }; + 04B525B82194D44E00FA37FD /* MSConstants+Flags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MSConstants+Flags.h"; sourceTree = ""; }; 04B7BBEE1E5FAD4D001A0CE1 /* MSIngestionUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSIngestionUtilTests.m; sourceTree = ""; }; 04BED70E1ECB63A100E20975 /* OCHamcrest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCHamcrest.framework; path = ../../Vendor/macOS/OCHamcrest/OCHamcrest.framework; sourceTree = ""; }; 04F21A041ECB73EE0074978D /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OHHTTPStubs.framework; path = ../../macOS/OHHTTPStubs/OHHTTPStubs.framework; sourceTree = ""; }; 04F3613C1FE1A92000A5A6D2 /* MSNoAutoAssignSessionIdLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSNoAutoAssignSessionIdLog.h; sourceTree = ""; }; 04FD126A1E4103CC007ABFE7 /* MSKeychainUtilTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSKeychainUtilTests.m; sourceTree = ""; }; + 266ED9D2BA796BA0329F4FC7 /* MSCustomPropertiesPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCustomPropertiesPrivate.h; sourceTree = ""; }; + 2DA030746930EE3DCB6A21B3 /* MSMetadataExtension.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMetadataExtension.m; sourceTree = ""; }; 2DA030B94CD3D38725671A79 /* MSChannelUnitDefaultPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSChannelUnitDefaultPrivate.h; sourceTree = ""; }; + 2DA038EE68EEE1414A270ADA /* MSMetadataExtension.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSMetadataExtension.h; sourceTree = ""; }; 3511AE7C20C5FF150056A739 /* MSMockLogWithConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSMockLogWithConversion.h; sourceTree = ""; }; 3513432D2056FF9000E6DC7D /* MSAbstractLogPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSAbstractLogPrivate.h; sourceTree = ""; }; 354274162012AE4000BE766F /* MSChannelProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSChannelProtocol.h; sourceTree = ""; }; @@ -884,6 +917,10 @@ 35DFC21A2170044600455589 /* MSStringTypedPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStringTypedPropertyTests.m; sourceTree = ""; }; 35DFC21B2170044600455589 /* MSLongTypedPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSLongTypedPropertyTests.m; sourceTree = ""; }; 35DFC21C2170044600455589 /* MSTypedPropertyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSTypedPropertyTests.m; sourceTree = ""; }; + 38032085217E85A90089772A /* MSDelegateForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSDelegateForwarder.h; sourceTree = ""; }; + 38032086217E85A90089772A /* MSDelegateForwarder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSDelegateForwarder.m; sourceTree = ""; }; + 3803208D217E8BD40089772A /* MSDelegateForwarderPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSDelegateForwarderPrivate.h; sourceTree = ""; }; + 38032091217E9DC50089772A /* MSCustomDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSCustomDelegate.h; sourceTree = ""; }; 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSUtilityTests.m; sourceTree = ""; }; 38148D8420D07FB70046257E /* MSCompression.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCompression.h; sourceTree = ""; }; 38148D8520D07FB70046257E /* MSCompression.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCompression.m; sourceTree = ""; }; @@ -902,6 +939,8 @@ 3849BA7B1EF3145A0072E3E0 /* MSDBStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDBStorage.m; sourceTree = ""; }; 3849BA7D1EF3489D0072E3E0 /* MSDBStorageTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDBStorageTests.m; sourceTree = ""; }; 3849BA841EF35D830072E3E0 /* MSDBStoragePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSDBStoragePrivate.h; sourceTree = ""; }; + 384A925F2188BE400099BE70 /* MSDelegateForwarderTestUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSDelegateForwarderTestUtil.h; sourceTree = ""; }; + 384A92602188BE400099BE70 /* MSDelegateForwarderTestUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSDelegateForwarderTestUtil.m; sourceTree = ""; }; 385AD9211D95898D008B354A /* MSServiceAbstractProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSServiceAbstractProtected.h; sourceTree = ""; }; 385E319920C1B90700BE7271 /* MSACModelConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSACModelConstants.h; sourceTree = ""; }; 385E319A20C1B90700BE7271 /* MSACModelConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSACModelConstants.m; sourceTree = ""; }; @@ -922,7 +961,6 @@ 38BC346C20B8CB0C00119C05 /* MSLogConversion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSLogConversion.h; sourceTree = ""; }; 38C4471E1ECE5352002E1B11 /* MSAppDelegateForwarder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAppDelegateForwarder.h; sourceTree = ""; }; 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAppDelegateForwarder.m; sourceTree = ""; }; - 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAppDelegateForwarderPrivate.h; sourceTree = ""; }; 38C633C61FDF15DD005F40C9 /* MSCustomApplicationDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCustomApplicationDelegate.h; sourceTree = ""; }; 38C633C81FDF163D005F40C9 /* MSAppDelegateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAppDelegateUtil.h; sourceTree = ""; }; 38E1B6791DDE3FDF000EFED1 /* MSAppCenterPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAppCenterPrivate.h; sourceTree = ""; }; @@ -1001,7 +1039,7 @@ B2FD53641E567BCF0050F909 /* MSDeviceHistoryInfoTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSDeviceHistoryInfoTests.m; sourceTree = ""; }; BA6822A89D38F04D8D95CE83 /* MSAppCenterIngestion.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAppCenterIngestion.m; sourceTree = ""; }; BA6824A001520825F18DFC42 /* MSAppCenterIngestion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAppCenterIngestion.h; sourceTree = ""; }; - D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStoragePerfomanceTests.m; sourceTree = ""; }; + D36136821E7BB338004AE043 /* MSStoragePerformanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStoragePerformanceTests.m; sourceTree = ""; }; D377A30B1E83A04600B2C97A /* MSMockUserDefaults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSMockUserDefaults.h; sourceTree = ""; }; D377A30C1E83A05900B2C97A /* MSMockUserDefaults.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockUserDefaults.m; sourceTree = ""; }; D38023E61E6EFC7C00466558 /* MSStartServiceLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartServiceLog.m; sourceTree = ""; }; @@ -1059,11 +1097,12 @@ E8E48F941D50FF4300A8C1B0 /* MSIngestionCallDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSIngestionCallDelegate.h; sourceTree = ""; }; F803BBF11E8E3677004B1E7A /* MSCustomProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSCustomProperties.h; sourceTree = ""; }; F803BBF21E8E3677004B1E7A /* MSCustomProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCustomProperties.m; sourceTree = ""; }; - F803BBF51E8E383E004B1E7A /* MSCustomPropertiesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSCustomPropertiesPrivate.h; sourceTree = ""; }; + F803BBF51E8E383E004B1E7A /* MSCustomPropertiesInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSCustomPropertiesInternal.h; sourceTree = ""; }; F803BBF81E8E3989004B1E7A /* MSCustomPropertiesLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCustomPropertiesLog.m; sourceTree = ""; }; F803BBFA1E8E39AB004B1E7A /* MSCustomPropertiesLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSCustomPropertiesLog.h; sourceTree = ""; }; F803BC371E8E6927004B1E7A /* MSCustomPropertiesTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCustomPropertiesTests.m; sourceTree = ""; }; F803BC391E8E6963004B1E7A /* MSCustomPropertiesLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSCustomPropertiesLogTests.m; sourceTree = ""; }; + F82E4C6C217F159A00EDAB34 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1228,7 +1267,7 @@ name = iOS; sourceTree = ""; }; - 35DFC18C217002D300455589 /* Properties */ = { + 354B79E0217109CE002183B5 /* Properties */ = { isa = PBXGroup; children = ( 35DFC1C72170030C00455589 /* MSBooleanTypedProperty.h */, @@ -1244,8 +1283,7 @@ 35DFC1BF2170030C00455589 /* MSTypedProperty.h */, 35DFC1C32170030C00455589 /* MSTypedProperty.m */, ); - name = Properties; - path = ../../../../AppCenterAnalytics/AppCenterAnalytics/Internals/Model/Properties; + path = Properties; sourceTree = ""; }; 381C91E21D3DB65D004512F1 /* Device */ = { @@ -1273,16 +1311,19 @@ name = Model; sourceTree = ""; }; - 38C4471C1ECE5352002E1B11 /* AppDelegate */ = { + 38C4471C1ECE5352002E1B11 /* DelegateForwarder */ = { isa = PBXGroup; children = ( 38C633C81FDF163D005F40C9 /* MSAppDelegateUtil.h */, 38C4471E1ECE5352002E1B11 /* MSAppDelegateForwarder.h */, 38C4471F1ECE5352002E1B11 /* MSAppDelegateForwarder.m */, - 38C447201ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h */, 38C633C61FDF15DD005F40C9 /* MSCustomApplicationDelegate.h */, + 3803208D217E8BD40089772A /* MSDelegateForwarderPrivate.h */, + 38032085217E85A90089772A /* MSDelegateForwarder.h */, + 38032086217E85A90089772A /* MSDelegateForwarder.m */, + 38032091217E9DC50089772A /* MSCustomDelegate.h */, ); - path = AppDelegate; + path = DelegateForwarder; sourceTree = ""; }; 6E0400FA1D1C98220051BCFA = { @@ -1317,6 +1358,7 @@ 3889932E1E29829700C27B36 /* MSAppCenterErrors.h */, 388993391E29B26D00C27B36 /* MSAppCenterErrors.m */, 6E04013F1D1C99AC0051BCFA /* MSConstants.h */, + 04B525B82194D44E00FA37FD /* MSConstants+Flags.h */, F803BBF11E8E3677004B1E7A /* MSCustomProperties.h */, F803BBF21E8E3677004B1E7A /* MSCustomProperties.m */, 3542742220167DA400BE766F /* MSEnable.h */, @@ -1340,13 +1382,14 @@ 6E0401581D1C9CFB0051BCFA /* AppCenter+Internal.h */, 6E0401441D1C99AC0051BCFA /* MSAppCenterInternal.h */, 38E1B6791DDE3FDF000EFED1 /* MSAppCenterPrivate.h */, - F803BBF51E8E383E004B1E7A /* MSCustomPropertiesPrivate.h */, + F803BBF51E8E383E004B1E7A /* MSCustomPropertiesInternal.h */, + 266ED9D2BA796BA0329F4FC7 /* MSCustomPropertiesPrivate.h */, 3592ABA51DC90E3600EF4592 /* MSLoggerInternal.h */, 387C757F1D6270A300D68CC1 /* MSServiceAbstractInternal.h */, 385AD9211D95898D008B354A /* MSServiceAbstractProtected.h */, 387C758D1D64DF2500D68CC1 /* MSServiceCommon.h */, 6E0401471D1C99AC0051BCFA /* MSServiceInternal.h */, - 38C4471C1ECE5352002E1B11 /* AppDelegate */, + 38C4471C1ECE5352002E1B11 /* DelegateForwarder */, E84B8E241D234EC7006FD231 /* Channel */, 381C91E21D3DB65D004512F1 /* Device */, 6E171B511D234717000DC480 /* Model */, @@ -1450,7 +1493,7 @@ 38BC346C20B8CB0C00119C05 /* MSLogConversion.h */, 385E319920C1B90700BE7271 /* MSACModelConstants.h */, 385E319A20C1B90700BE7271 /* MSACModelConstants.m */, - 35DFC18C217002D300455589 /* Properties */, + 354B79E0217109CE002183B5 /* Properties */, ); path = Model; sourceTree = ""; @@ -1475,13 +1518,14 @@ 04FD126A1E4103CC007ABFE7 /* MSKeychainUtilTests.m */, 5C7877911EA0CFF3002263CC /* MSLogDBStorageTests.m */, B24F3F161D93A3FF00827213 /* MSLoggerTests.m */, + 04A20D70217660D50096723C /* MSWrapperLoggerTests.m */, 045660FA1D99EEEB002F7055 /* MSLogWithPropertiesTests.m */, E79750EA20A50BF400E3EAE8 /* MSOneCollectorChannelDelegateTests.m */, 04A79A4720C6F635006D0072 /* MSOneCollectorIngestionTests.m */, 04B7BBEE1E5FAD4D001A0CE1 /* MSIngestionUtilTests.m */, 387C75951D64EE1900D68CC1 /* MSServiceAbstractTests.m */, 0493783F1FE4913C000ADBAF /* MSSessionContextTests.m */, - D36136821E7BB338004AE043 /* MSStoragePerfomanceTests.m */, + D36136821E7BB338004AE043 /* MSStoragePerformanceTests.m */, B26D4DD6211B9B5D00AB4E28 /* MSTicketCacheTests.m */, 380A4DCA1DD6908A00E99219 /* MSUtilityTests.m */, 6E2395831D22EF5F00E543C8 /* Frameworks */, @@ -1494,6 +1538,7 @@ 6E2395831D22EF5F00E543C8 /* Frameworks */ = { isa = PBXGroup; children = ( + F82E4C6C217F159A00EDAB34 /* sqlite3.c */, 04BED70B1ECB636900E20975 /* OCHamcrest */, 6E171B121D22FC2E000DC480 /* OCMock */, 6E171B221D22FF40000DC480 /* OHHTTPStubs */, @@ -1523,6 +1568,8 @@ 3511AE7C20C5FF150056A739 /* MSMockLogWithConversion.h */, B27B4125214C939D007CAE9C /* MSStorageTestUtil.h */, B27B4126214C939D007CAE9C /* MSStorageTestUtil.m */, + 384A925F2188BE400099BE70 /* MSDelegateForwarderTestUtil.h */, + 384A92602188BE400099BE70 /* MSDelegateForwarderTestUtil.m */, ); path = Util; sourceTree = ""; @@ -1544,6 +1591,8 @@ 359C38DC214079D90066C509 /* MSDeviceExtension.m */, E74B14A820B36B9B002C0183 /* MSLocExtension.h */, E74B14A920B36B9B002C0183 /* MSLocExtension.m */, + 2DA038EE68EEE1414A270ADA /* MSMetadataExtension.h */, + 2DA030746930EE3DCB6A21B3 /* MSMetadataExtension.m */, E74B14D020B36EC5002C0183 /* MSNetExtension.h */, E74B14D120B36EC5002C0183 /* MSNetExtension.m */, E74B14B820B36C2B002C0183 /* MSOSExtension.h */, @@ -1752,12 +1801,15 @@ 041EFD261EE1CA98006DCD56 /* MSAbstractLog.h in Headers */, E79750E920A4F41400E3EAE8 /* MSOneCollectorChannelDelegatePrivate.h in Headers */, 04F3613F1FE1A95700A5A6D2 /* MSNoAutoAssignSessionIdLog.h in Headers */, + 38032094217E9DC50089772A /* MSCustomDelegate.h in Headers */, B2CD74871F22BD910070E7DF /* MSLogDBStorage.h in Headers */, 043120B91EE0BCF9007054C5 /* MSStorage.h in Headers */, E74B14C420B36C66002C0183 /* MSAppExtension.h in Headers */, 043120BA1EE0BCF9007054C5 /* MSLoggerInternal.h in Headers */, 043120BE1EE0BCF9007054C5 /* MSDeviceTrackerPrivate.h in Headers */, + 38032090217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */, B2CD748C1F22BDA70070E7DF /* MSDBStorage.h in Headers */, + 04B525BB2194D45600FA37FD /* MSConstants+Flags.h in Headers */, 043120C01EE0BCF9007054C5 /* AppCenter.h in Headers */, 38148D8B20D07FD90046257E /* MSCompression.h in Headers */, 043120C11EE0BCF9007054C5 /* MSHttpIngestion.h in Headers */, @@ -1767,9 +1819,9 @@ E74B14BC20B36C2B002C0183 /* MSOSExtension.h in Headers */, 045C85121FE0B2D300089540 /* MSSessionContext.h in Headers */, 043120C71EE0BCF9007054C5 /* MSAppCenterInternal.h in Headers */, + 38032089217E85A90089772A /* MSDelegateForwarder.h in Headers */, 043120C81EE0BCF9007054C5 /* MSService.h in Headers */, B2ADB1442124C13300D0D7D9 /* MSOneCollectorIngestionPrivate.h in Headers */, - 045C0BF01EEB12320038FD6B /* MSAppDelegateForwarderPrivate.h in Headers */, B2CD74CB1F22BF8D0070E7DF /* MSLogDBStoragePrivate.h in Headers */, E7D23C6820B6391700A47D62 /* MSCSExtensions.h in Headers */, E7D23C5F20B4EC3700A47D62 /* MSSerializableObject.h in Headers */, @@ -1790,6 +1842,7 @@ 043120D61EE0BCF9007054C5 /* MSAppCenterIngestion.h in Headers */, 2DA0322920F747CAB5668AAA /* MSChannelUnitDefaultPrivate.h in Headers */, 35DFC1E32170030C00455589 /* MSDateTimeTypedProperty.h in Headers */, + 2DA033F72EB5B6AA6990DA39 /* MSMetadataExtension.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1801,6 +1854,7 @@ 04754F6D1EA98182002CBA46 /* MSHttpIngestionPrivate.h in Headers */, 04A79A4320C6F432006D0072 /* MSOneCollectorIngestion.h in Headers */, 35DFC1E22170030C00455589 /* MSDateTimeTypedProperty.h in Headers */, + 38032093217E9DC50089772A /* MSCustomDelegate.h in Headers */, E7D23C5E20B4EC3700A47D62 /* MSSerializableObject.h in Headers */, 04754F6E1EA98182002CBA46 /* MSIngestionCall.h in Headers */, 04754F6F1EA98182002CBA46 /* MSIngestionProtocol.h in Headers */, @@ -1815,6 +1869,7 @@ B2CD74581F22BB710070E7DF /* MSWrapperSdk.h in Headers */, 04754F7A1EA98182002CBA46 /* MSIngestionUtil.h in Headers */, 04754F9C1EA98182002CBA46 /* MSWrapperLogger.h in Headers */, + 3803208F217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */, 3568A7E7201A91A300F3B860 /* MSLogger.h in Headers */, E74B14D320B36EC5002C0183 /* MSNetExtension.h in Headers */, 38148D8A20D07FD80046257E /* MSCompression.h in Headers */, @@ -1833,6 +1888,7 @@ 3568A7E2201A8A6300F3B860 /* MSChannelUnitDefault.h in Headers */, 3568A7E4201A8A6300F3B860 /* MSChannelUnitProtocol.h in Headers */, 3814A8E320BF5E790093AF45 /* MSCSEpochAndSeq.h in Headers */, + 04B525BA2194D45500FA37FD /* MSConstants+Flags.h in Headers */, 04754F7B1EA98182002CBA46 /* MSIngestionCallDelegate.h in Headers */, 35DFC1D02170030C00455589 /* MSTypedProperty.h in Headers */, B2CD74861F22BD910070E7DF /* MSLogDBStorage.h in Headers */, @@ -1867,6 +1923,7 @@ 385E319C20C1B90700BE7271 /* MSACModelConstants.h in Headers */, B2CD749E1F22BE270070E7DF /* MSConstants+Internal.h in Headers */, B23507B92118D22800F98D4F /* MSTicketCache.h in Headers */, + 38032088217E85A90089772A /* MSDelegateForwarder.h in Headers */, B2CD74BC1F22BE270070E7DF /* MSUtility+File.h in Headers */, B2CD74CA1F22BF8C0070E7DF /* MSLogDBStoragePrivate.h in Headers */, E74B14E620B370A5002C0183 /* MSUserExtension.h in Headers */, @@ -1879,7 +1936,6 @@ 04754F971EA98182002CBA46 /* MSChannelUnitConfiguration.h in Headers */, 04F3613E1FE1A95600A5A6D2 /* MSNoAutoAssignSessionIdLog.h in Headers */, 04754F981EA98182002CBA46 /* MSAppCenterInternal.h in Headers */, - 04873E841F2FCF9000A13AFF /* MSAppDelegateForwarderPrivate.h in Headers */, 044864C51EBD12F200031019 /* MSCustomProperties.h in Headers */, 04754F991EA98182002CBA46 /* MSService.h in Headers */, B2CD74A71F22BE270070E7DF /* MSUtility+Application.h in Headers */, @@ -1904,6 +1960,7 @@ 04754FA41EA98182002CBA46 /* MSKeychainUtil.h in Headers */, 04754FA71EA98182002CBA46 /* MSAppCenterIngestion.h in Headers */, 2DA03DF6ADD26CAC19463B5C /* MSChannelUnitDefaultPrivate.h in Headers */, + 2DA03F97D9D7591A1473C5B6 /* MSMetadataExtension.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1917,6 +1974,7 @@ 35DFC1E12170030C00455589 /* MSDateTimeTypedProperty.h in Headers */, E7D23C5D20B4EC3700A47D62 /* MSSerializableObject.h in Headers */, E80EB1071D50273700C9003F /* MSIngestionCall.h in Headers */, + 38032087217E85A90089772A /* MSDelegateForwarder.h in Headers */, E84B8E321D235209006FD231 /* MSIngestionProtocol.h in Headers */, 3844FF1F1E8C2716003E9194 /* MSLogWithProperties.h in Headers */, 387C758F1D64E50800D68CC1 /* MSServiceCommon.h in Headers */, @@ -1937,6 +1995,7 @@ E7D23C6620B6391700A47D62 /* MSCSExtensions.h in Headers */, E74B14BA20B36C2B002C0183 /* MSOSExtension.h in Headers */, 3542742320167DD500BE766F /* MSEnable.h in Headers */, + 3803208E217E8BD40089772A /* MSDelegateForwarderPrivate.h in Headers */, 354274202016769C00BE766F /* MSChannelGroupProtocol.h in Headers */, B2787213201AAA7F004AA9A5 /* MSAppCenterErrors.h in Headers */, 358F9BC920195F3300B9E22C /* MSLogger.h in Headers */, @@ -1969,8 +2028,10 @@ E7D23C5620B4E38B00A47D62 /* MSCSData.h in Headers */, B2ADB1422124C13300D0D7D9 /* MSOneCollectorIngestionPrivate.h in Headers */, 80B7EA2220CA9CBF00DF524C /* MSEncrypter.h in Headers */, + 04B525B92194D44E00FA37FD /* MSConstants+Flags.h in Headers */, D38023E91E6EFC7C00466558 /* MSStartServiceLog.h in Headers */, E74B149920B364EE002C0183 /* MSCommonSchemaLog.h in Headers */, + 38032092217E9DC50089772A /* MSCustomDelegate.h in Headers */, 387C76FD1D6C9CF100D68CC1 /* MSServiceAbstract.h in Headers */, B2CD74A61F22BE270070E7DF /* MSUtility+Application.h in Headers */, E74B14C220B36C66002C0183 /* MSAppExtension.h in Headers */, @@ -1996,7 +2057,6 @@ E84B8E351D235226006FD231 /* MSHttpIngestion.h in Headers */, 6E171B601D234717000DC480 /* MSLogContainer.h in Headers */, B2CD74C11F22BE270070E7DF /* MSUtility+StringFormatting.h in Headers */, - 38C447241ECE5352002E1B11 /* MSAppDelegateForwarderPrivate.h in Headers */, B2CD74BB1F22BE270070E7DF /* MSUtility+File.h in Headers */, 6EF628F41D371B1600CAFF64 /* MSChannelUnitConfiguration.h in Headers */, 6E04016F1D1C9E460051BCFA /* MSAppCenterInternal.h in Headers */, @@ -2018,6 +2078,8 @@ 38FD798F1EB7B0B800DE2FB3 /* MSAppCenter.h in Headers */, 049378391FE44539000ADBAF /* MSSessionContextPrivate.h in Headers */, 2DA034555E2CB013B268C82C /* MSChannelUnitDefaultPrivate.h in Headers */, + 2DA03417C2DCD4BF8D03D3EF /* MSMetadataExtension.h in Headers */, + 266EDC94C8B60B195B13BF40 /* MSCustomPropertiesPrivate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2380,6 +2442,7 @@ 043120861EE0BCF9007054C5 /* MSAbstractLog.m in Sources */, 043120871EE0BCF9007054C5 /* MSKeychainUtil.m in Sources */, 35DFC1EC2170030C00455589 /* MSLongTypedProperty.m in Sources */, + 3803208C217E85A90089772A /* MSDelegateForwarder.m in Sources */, E7D23C7920B6549E00A47D62 /* MSCSModelConstants.m in Sources */, B2CD748A1F22BD910070E7DF /* MSLogDBStorage.m in Sources */, 043120881EE0BCF9007054C5 /* MSWrapperLogger.m in Sources */, @@ -2396,6 +2459,7 @@ 0431208F1EE0BCF9007054C5 /* MSUserDefaults.m in Sources */, 043120911EE0BCF9007054C5 /* MSDeviceTracker.m in Sources */, 043120951EE0BCF9007054C5 /* MSAppCenterIngestion.m in Sources */, + 2DA035A646596E34DAD3E440 /* MSMetadataExtension.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2416,10 +2480,12 @@ 0446DF091F3B864600C8E338 /* MSAppDelegateForwarderTests.m in Sources */, 0446DF0A1F3B864600C8E338 /* MSChannelUnitDefaultTests.m in Sources */, 0446DF0B1F3B864600C8E338 /* MSAppCenterTests.m in Sources */, - 0446DF0C1F3B864600C8E338 /* MSStoragePerfomanceTests.m in Sources */, + 0446DF0C1F3B864600C8E338 /* MSStoragePerformanceTests.m in Sources */, 0446DF0D1F3B864600C8E338 /* MSLogDBStorageTests.m in Sources */, 35DFC2302170044A00455589 /* MSBooleanTypedPropertyTests.m in Sources */, 04A79A4B20C6F63F006D0072 /* MSOneCollectorIngestionTests.m in Sources */, + 384A92632188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */, + 04A20D74217660E40096723C /* MSWrapperLoggerTests.m in Sources */, 35DFC2352170051600455589 /* MSDoubleTypedPropertyTests.m in Sources */, 0446DF0E1F3B864600C8E338 /* MSHttpTestUtil.m in Sources */, 0446DF0F1F3B864600C8E338 /* MSDeviceHistoryInfoTests.m in Sources */, @@ -2436,6 +2502,7 @@ 0446DF161F3B864600C8E338 /* MSStartServiceLogTests.m in Sources */, 0446DF171F3B864600C8E338 /* MSServiceAbstractTests.m in Sources */, 0446DF181F3B864600C8E338 /* MSChannelUnitConfigurationTests.m in Sources */, + F82E4C6F217F159A00EDAB34 /* sqlite3.c in Sources */, B26D4DBC211B5BE300AB4E28 /* MSMockCommonSchemaLog.m in Sources */, E7D23C5320B4E0CA00A47D62 /* MSCommonSchemaLogTests.m in Sources */, 0446DF191F3B864600C8E338 /* MSLoggerTests.m in Sources */, @@ -2455,7 +2522,8 @@ 0446DF321F3B870700C8E338 /* MSDBStorageTests.m in Sources */, E7D23C7020B6412300A47D62 /* MSCSExtensionsTests.m in Sources */, 04A140871ECE63BB001CEE94 /* MSServiceAbstractTests.m in Sources */, - 0446DF311F3B86FE00C8E338 /* MSStoragePerfomanceTests.m in Sources */, + 0446DF311F3B86FE00C8E338 /* MSStoragePerformanceTests.m in Sources */, + 04A20D73217660E30096723C /* MSWrapperLoggerTests.m in Sources */, 38FDFF6B2109409900E17269 /* MSMockKeychainUtil.m in Sources */, 04A79A4A20C6F63E006D0072 /* MSOneCollectorIngestionTests.m in Sources */, 35DFC2402170053100455589 /* MSTypedPropertyTests.m in Sources */, @@ -2477,6 +2545,7 @@ 04A1408B1ECE63CB001CEE94 /* MSAppCenterIngestionTests.m in Sources */, 35C0E3CC1FD6146A004E841E /* MSMockSecondService.m in Sources */, 046AEAE41ECA562A00CBE511 /* MSStartServiceLogTests.m in Sources */, + F82E4C6E217F159A00EDAB34 /* sqlite3.c in Sources */, B26D4DBB211B5BE300AB4E28 /* MSMockCommonSchemaLog.m in Sources */, 0446DF331F3B870A00C8E338 /* MSLogDBStorageTests.m in Sources */, 046AEAE61ECA562A00CBE511 /* MSChannelUnitConfigurationTests.m in Sources */, @@ -2487,6 +2556,7 @@ 046AEAE91ECA562A00CBE511 /* MSIngestionUtilTests.m in Sources */, 04A1408A1ECE63C7001CEE94 /* MSLogContainerTests.m in Sources */, 049BC8431ECE524D00FB6719 /* MSDeviceTrackerTests.m in Sources */, + 384A92622188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */, B2CD74CE1F22BFD00070E7DF /* MSMockService.m in Sources */, 35DFC2392170052000455589 /* MSLongTypedPropertyTests.m in Sources */, 04873E871F2FDDEC00A13AFF /* MSAppDelegateForwarderTests.m in Sources */, @@ -2538,6 +2608,7 @@ B2CD74C51F22BE270070E7DF /* MSUtility+StringFormatting.m in Sources */, 049BC8421ECE4EDB00FB6719 /* MSDeviceTracker.m in Sources */, B2CD74BF1F22BE270070E7DF /* MSUtility+File.m in Sources */, + 3803208B217E85A90089772A /* MSDelegateForwarder.m in Sources */, 04754F571EA98182002CBA46 /* MSStartServiceLog.m in Sources */, 38148DB920D184AC0046257E /* MSCompression.m in Sources */, E7D23C5A20B4E38B00A47D62 /* MSCSData.m in Sources */, @@ -2565,6 +2636,7 @@ 35DFC1D92170030C00455589 /* MSBooleanTypedProperty.m in Sources */, B2CD74891F22BD910070E7DF /* MSLogDBStorage.m in Sources */, B2CD74AA1F22BE270070E7DF /* MSUtility+Application.m in Sources */, + 2DA03E86D60D8AC11B345613 /* MSMetadataExtension.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2608,6 +2680,7 @@ E84B8E301D2351DB006FD231 /* MSChannelGroupDefault.m in Sources */, B2CD74BE1F22BE270070E7DF /* MSUtility+File.m in Sources */, D38023E81E6EFC7C00466558 /* MSStartServiceLog.m in Sources */, + 3803208A217E85A90089772A /* MSDelegateForwarder.m in Sources */, E8753D6F1D4BE53F00241513 /* MSIngestionUtil.m in Sources */, E7D23C5920B4E38B00A47D62 /* MSCSData.m in Sources */, F803BBF91E8E3989004B1E7A /* MSCustomPropertiesLog.m in Sources */, @@ -2635,6 +2708,7 @@ 35DFC1D82170030C00455589 /* MSBooleanTypedProperty.m in Sources */, 38148D8720D07FB70046257E /* MSCompression.m in Sources */, B2CD74A91F22BE270070E7DF /* MSUtility+Application.m in Sources */, + 2DA03EFBAD1AC21D07F02509 /* MSMetadataExtension.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2654,7 +2728,7 @@ 38641B051EB0F40800B2CE73 /* MSAppDelegateForwarderTests.m in Sources */, 6EB1F40E1D2443B7005F9F99 /* MSChannelUnitDefaultTests.m in Sources */, 384959D51D491D4F008F6B3A /* MSAppCenterTests.m in Sources */, - D36136831E7BB338004AE043 /* MSStoragePerfomanceTests.m in Sources */, + D36136831E7BB338004AE043 /* MSStoragePerformanceTests.m in Sources */, 38A3891C212B6E3C00F1C0D8 /* MSDeadLockTests.m in Sources */, 5C7877921EA0CFF3002263CC /* MSLogDBStorageTests.m in Sources */, 35DFC2332170051100455589 /* MSDateTimeTypedPropertyTests.m in Sources */, @@ -2667,9 +2741,11 @@ 38FDFF6A2109409900E17269 /* MSMockKeychainUtil.m in Sources */, 35C0E3CD1FD6146A004E841E /* MSMockSecondService.m in Sources */, E829E4231D25C8BA00F19DA1 /* MSAppCenterIngestionTests.m in Sources */, + 04A20D72217660E10096723C /* MSWrapperLoggerTests.m in Sources */, 805F3F6B1F209C9D00B489E4 /* MSMockService.m in Sources */, E88EBBFB1D2C8CC7007E7785 /* MSLogContainerTests.m in Sources */, 04FD126B1E4103CC007ABFE7 /* MSKeychainUtilTests.m in Sources */, + 384A92612188BE400099BE70 /* MSDelegateForwarderTestUtil.m in Sources */, D38024121E7130C700466558 /* MSStartServiceLogTests.m in Sources */, 387C75961D64EE1900D68CC1 /* MSServiceAbstractTests.m in Sources */, 35DFC23F2170052F00455589 /* MSTypedPropertyTests.m in Sources */, @@ -2681,6 +2757,7 @@ E88D17061D35B6B500A5EA57 /* MSMockLog.m in Sources */, 049378411FE4914B000ADBAF /* MSSessionContextTests.m in Sources */, 8087362C20C1DE1B004C4157 /* MSEncrypterTests.m in Sources */, + F82E4C6D217F159A00EDAB34 /* sqlite3.c in Sources */, B26D4DD7211B9B5E00AB4E28 /* MSTicketCacheTests.m in Sources */, 6E48A5A71D383893006E8B5F /* MSChannelGroupDefaultTests.m in Sources */, 35DFC2372170051900455589 /* MSDoubleTypedPropertyTests.m in Sources */, diff --git a/AppCenter/AppCenter/AppCenter.h b/AppCenter/AppCenter/AppCenter.h index bc36522f44..3aad760135 100644 --- a/AppCenter/AppCenter/AppCenter.h +++ b/AppCenter/AppCenter/AppCenter.h @@ -3,7 +3,6 @@ #import "MSAbstractLog.h" #import "MSAppCenter.h" #import "MSAppCenterErrors.h" -#import "MSChannelDelegate.h" #import "MSChannelGroupProtocol.h" #import "MSChannelProtocol.h" #import "MSConstants.h" diff --git a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.h b/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.h deleted file mode 100644 index c68db07923..0000000000 --- a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.h +++ /dev/null @@ -1,53 +0,0 @@ -#import "MSCustomApplicationDelegate.h" - -NS_ASSUME_NONNULL_BEGIN - -/** - * Enum used to represent all kind of executors running the completion handler. - */ -typedef NS_OPTIONS(NSUInteger, MSCompletionExecutor) { - MSCompletionExecutorNone = (1 << 0), - MSCompletionExecutorOriginal = (1 << 1), - MSCompletionExecutorCustom = (1 << 2), - MSCompletionExecutorForwarder = (1 << 3) -}; - -@interface MSAppDelegateForwarder : NSObject - -/** - * Enable/Disable Application forwarding. - */ -@property(nonatomic, class) BOOL enabled; - -/** - * Add a delegate. This method is thread safe. - * - * @param delegate A delegate. - */ -+ (void)addDelegate:(id)delegate; - -/** - * Remove a delegate. This method is thread safe. - * - * @param delegate A delegate. - */ -+ (void)removeDelegate:(id)delegate; - -/** - * Add an app delegate selector to swizzle. - * - * @param selector An app delegate selector to swizzle. - * - * @discussion Due to the early registration of swizzling on the original app delegate each custom delegate must sign up for selectors to - * swizzle within the `load` method of a category over the @see MSAppDelegateForwarder class. - */ -+ (void)addAppDelegateSelectorToSwizzle:(SEL)selector; - -/** - * Flush debugging traces accumulated until now. - */ -+ (void)flushTraceBuffer; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.m b/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.m deleted file mode 100644 index 2914ed6324..0000000000 --- a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarder.m +++ /dev/null @@ -1,391 +0,0 @@ -#import - -#import "MSAppCenterInternal.h" -#import "MSAppDelegateForwarderPrivate.h" -#import "MSCustomApplicationDelegate.h" -#import "MSLogger.h" -#import "MSUtility+Application.h" - -static NSString *const kMSCustomSelectorPrefix = @"custom_"; -static NSString *const kMSReturnedValueSelectorPart = @"returnedValue:"; -static NSString *const kMSIsAppDelegateForwarderEnabledKey = @"AppCenterAppDelegateForwarderEnabled"; - -// Original selectors with special handling. -static NSString *const kMSOpenURLSourceApplicationAnnotation = @"application:openURL:sourceApplication:annotation:"; -static NSString *const kMSOpenURLOptions = @"application:openURL:options:"; - -static NSHashTable> *_delegates = nil; -static NSMutableSet *_selectorsToSwizzle = nil; -static NSDictionary *_deprecatedSelectors = nil; -static NSMutableDictionary *_originalImplementations = nil; -static NSMutableArray *traceBuffer = nil; -static IMP _originalSetDelegateImp = NULL; -static BOOL _enabled = YES; - -@implementation MSAppDelegateForwarder - -+ (void)initialize { - traceBuffer = [NSMutableArray new]; -} - -+ (void)load { - - /* - * The application starts querying its delegate for its implementation as soon as it is set then may never query again. It means that if - * the application delegate doesn't implement an optional method of the `UIApplicationDelegate` protocol at that time then that method may - * never be called even if added later via swizzling. This is why the application delegate swizzling should happen at the time it is set - * to the application object. - */ - NSNumber *appForwarderEnabledNum = [[NSBundle mainBundle] objectForInfoDictionaryKey:kMSIsAppDelegateForwarderEnabledKey]; - BOOL appForwarderEnabled = appForwarderEnabledNum ? [appForwarderEnabledNum boolValue] : YES; - MSAppDelegateForwarder.enabled = appForwarderEnabled; - - // Swizzle `setDelegate:` of Application class. - if (MSAppDelegateForwarder.enabled) { - [self addTraceBlock:^{ - MSLogDebug([MSAppCenter logTag], @"Application delegate forwarder is enabled. It may use swizzling."); - }]; - } else { - [self addTraceBlock:^{ - MSLogDebug([MSAppCenter logTag], @"Application delegate forwarder is disabled. It won't use swizzling."); - }]; - } -} - -+ (instancetype)sharedInstance { - static MSAppDelegateForwarder *sharedInstance = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sharedInstance = [self new]; - }); - return sharedInstance; -} - -#pragma mark - Accessors - -+ (NSHashTable> *)delegates { - return _delegates ?: (_delegates = [NSHashTable weakObjectsHashTable]); -} - -+ (void)setDelegates:(NSHashTable> *)delegates { - _delegates = delegates; -} - -+ (NSMutableSet *)selectorsToSwizzle { - return _selectorsToSwizzle ?: (_selectorsToSwizzle = [NSMutableSet new]); -} - -+ (NSDictionary *)deprecatedSelectors { - if (!_deprecatedSelectors) { -#if TARGET_OS_OSX - _deprecatedSelectors = @{}; -#else - _deprecatedSelectors = @{kMSOpenURLOptions : kMSOpenURLSourceApplicationAnnotation}; -#endif - } - return _deprecatedSelectors; -} - -+ (NSMutableDictionary *)originalImplementations { - return _originalImplementations ?: (_originalImplementations = [NSMutableDictionary new]); -} - -+ (void)addTraceBlock:(void (^)(void))block { - @synchronized(traceBuffer) { - if (traceBuffer) { - static dispatch_once_t onceToken = 0; - dispatch_once(&onceToken, ^{ - [traceBuffer addObject:^{ - MSLogVerbose([MSAppCenter logTag], @"Start buffering traces."); - }]; - }); - [traceBuffer addObject:block]; - } else { - block(); - } - } -} - -+ (IMP)originalSetDelegateImp { - return _originalSetDelegateImp; -} - -+ (void)setOriginalSetDelegateImp:(IMP)originalSetDelegateImp { - _originalSetDelegateImp = originalSetDelegateImp; -} - -+ (BOOL)enabled { - @synchronized(self) { - return _enabled; - } -} - -+ (void)setEnabled:(BOOL)enabled { - @synchronized(self) { - _enabled = enabled; - if (!enabled) { - [self.delegates removeAllObjects]; - } - } -} - -#pragma mark - Delegates - -+ (void)addDelegate:(id)delegate { - @synchronized(self) { - if (self.enabled) { - [self.delegates addObject:delegate]; - } - } -} - -+ (void)removeDelegate:(id)delegate { - @synchronized(self) { - if (self.enabled) { - [self.delegates removeObject:delegate]; - } - } -} - -#pragma mark - Swizzling - -+ (void)swizzleOriginalDelegate:(id)originalDelegate { - IMP originalImp = NULL; - Class delegateClass = [originalDelegate class]; - SEL originalSelector, customSelector; - - // Swizzle all registered selectors. - for (NSString *selectorString in self.selectorsToSwizzle) { - originalSelector = NSSelectorFromString(selectorString); - customSelector = NSSelectorFromString([kMSCustomSelectorPrefix stringByAppendingString:selectorString]); - originalImp = [self swizzleOriginalSelector:originalSelector withCustomSelector:customSelector originalClass:delegateClass]; - if (originalImp) { - - // Save the original implementation for later use. - self.originalImplementations[selectorString] = [NSValue valueWithBytes:&originalImp objCType:@encode(IMP)]; - } - } - [self.selectorsToSwizzle removeAllObjects]; -} - -+ (IMP)swizzleOriginalSelector:(SEL)originalSelector withCustomSelector:(SEL)customSelector originalClass:(Class)originalClass { - - // Replace original implementation - NSString *originalSelectorStr = NSStringFromSelector(originalSelector); - Method originalMethod = class_getInstanceMethod(originalClass, originalSelector); - IMP customImp = class_getMethodImplementation(self, customSelector); - IMP originalImp = NULL; - BOOL methodAdded = NO; - BOOL skipped = NO; - NSString *warningMsg; - NSString *remediationMsg = @"You need to explicitly call the App Center API from your app delegate implementation."; - - // Replace original implementation by the custom one. - if (originalMethod) { - originalImp = method_setImplementation(originalMethod, customImp); - } else if (![originalClass instancesRespondToSelector:originalSelector]) { - - // Check for deprecation. - NSString *deprecatedSelectorStr = self.deprecatedSelectors[originalSelectorStr]; - if (deprecatedSelectorStr && [originalClass instancesRespondToSelector:NSSelectorFromString(deprecatedSelectorStr)]) { - - // An implementation for the deprecated selector exists. Don't add the new method, it might eclipse the original implementation. - warningMsg = [NSString - stringWithFormat:@"No implementation found for this selector, though an implementation of its deprecated API '%@' exists.", - deprecatedSelectorStr]; - } else { - - // Skip this selector if it's deprecated and doesn't have an implementation. - if ([self.deprecatedSelectors.allValues containsObject:originalSelectorStr]) { - skipped = YES; - } else { - - /* - * The original class may not implement the selector (e.g.: optional method from protocol), add the method to the original class and - * associate it with the custom implementation. - */ - Method customMethod = class_getInstanceMethod(self, customSelector); - methodAdded = class_addMethod(originalClass, originalSelector, customImp, method_getTypeEncoding(customMethod)); - } - } - } - - /* - * If class instances respond to the selector but no implementation is found it's likely that the original class is doing message - * forwarding, in this case we can't add our implementation to the class or we will break the forwarding. - */ - - // Validate swizzling. - if (!skipped) { - if (!originalImp && !methodAdded) { - [self addTraceBlock:^{ - NSString *message = [NSString stringWithFormat:@"Cannot swizzle selector '%@' of class '%@'.", originalSelectorStr, originalClass]; - if (warningMsg) { - MSLogWarning([MSAppCenter logTag], @"%@ %@", message, warningMsg); - } else { - MSLogError([MSAppCenter logTag], @"%@ %@", message, remediationMsg); - } - }]; - } else { - [self addTraceBlock:^{ - MSLogDebug([MSAppCenter logTag], @"Selector '%@' of class '%@' is swizzled.", originalSelectorStr, originalClass); - }]; - } - } - return originalImp; -} - -+ (void)addAppDelegateSelectorToSwizzle:(SEL)selector { - if (self.enabled) { - - // Swizzle only once and only if needed. No selector to swizzle then no swizzling at all. - static dispatch_once_t appSwizzleOnceToken; - dispatch_once(&appSwizzleOnceToken, ^{ - MSAppDelegateForwarder.originalSetDelegateImp = [MSAppDelegateForwarder swizzleOriginalSelector:@selector(setDelegate:) - withCustomSelector:@selector(custom_setDelegate:) -#if TARGET_OS_OSX - originalClass:[NSApplication class]]; -#else - originalClass:[UIApplication class]]; -#endif - }); - - /* - * TODO: We could register custom delegate classes and then query those classes if they responds to selector. If so just add that - * selector to be swizzled. Just make sure it doesn't have an heavy impact on performances. - */ - [self.selectorsToSwizzle addObject:NSStringFromSelector(selector)]; - } -} - -#pragma mark - Custom Application - -- (void)custom_setDelegate:(id)delegate { - - // Swizzle only once. - static dispatch_once_t delegateSwizzleOnceToken; - dispatch_once(&delegateSwizzleOnceToken, ^{ - - // Swizzle the app delegate before it's actually set. - [MSAppDelegateForwarder swizzleOriginalDelegate:delegate]; - }); - - // Forward to the original `setDelegate:` implementation. - IMP originalImp = MSAppDelegateForwarder.originalSetDelegateImp; - if (originalImp) { - ((void (*)(id, SEL, id))originalImp)(self, _cmd, delegate); - } -} - -#pragma mark - Custom UIApplicationDelegate - -#if !TARGET_OS_OSX - -/* - * Those methods will never get called but their implementation will be used by swizzling. Those implementations will run within the - * delegate context. Meaning that `self` will point to the original app delegate and not this forwarder. - */ -- (BOOL)custom_application:(UIApplication *)application - openURL:(NSURL *)url - sourceApplication:(nullable NSString *)sourceApplication - annotation:(id)annotation { - BOOL result = NO; - IMP originalImp = NULL; - - // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; - if (originalImp) { - result = ((BOOL(*)(id, SEL, UIApplication *, NSURL *, NSString *, id))originalImp)(self, _cmd, application, url, sourceApplication, - annotation); - } - - // Forward to custom delegates. - return [[MSAppDelegateForwarder sharedInstance] application:application - openURL:url - sourceApplication:sourceApplication - annotation:annotation - returnedValue:result]; -} - -- (BOOL)custom_application:(UIApplication *)application - openURL:(nonnull NSURL *)url - options:(nonnull NSDictionary *)options { - BOOL result = NO; - IMP originalImp = NULL; - - // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; - if (originalImp) { - result = ((BOOL(*)(id, SEL, UIApplication *, NSURL *, NSDictionary *))originalImp)( - self, _cmd, application, url, options); - } - - // Forward to custom delegates. - return [[MSAppDelegateForwarder sharedInstance] application:application openURL:url options:options returnedValue:result]; -} -#endif - -#pragma mark - Forwarding - -- (void)forwardInvocation:(NSInvocation *)invocation { - @synchronized([self class]) { - BOOL forwarded = NO; - BOOL hasReturnedValue = ([NSStringFromSelector(invocation.selector) hasSuffix:kMSReturnedValueSelectorPart]); - NSUInteger returnedValueIdx = 0; - void *returnedValuePtr = NULL; - - // Prepare returned value if any. - if (hasReturnedValue) { - - // Returned value argument is always the last one. - returnedValueIdx = invocation.methodSignature.numberOfArguments - 1; - returnedValuePtr = malloc(invocation.methodSignature.methodReturnLength); - } - - // Forward to delegates executing a custom method. - for (id delegate in [self class].delegates) { - if ([delegate respondsToSelector:invocation.selector]) { - [invocation invokeWithTarget:delegate]; - - // Chaining return values. - if (hasReturnedValue) { - [invocation getReturnValue:returnedValuePtr]; - [invocation setArgument:returnedValuePtr atIndex:returnedValueIdx]; - } - forwarded = YES; - } - } - - // Forward back the original return value if no delegates to receive the message. - if (hasReturnedValue && !forwarded) { - [invocation getArgument:returnedValuePtr atIndex:returnedValueIdx]; - [invocation setReturnValue:returnedValuePtr]; - } - free(returnedValuePtr); - } -} - -#pragma mark - Logging - -+ (void)flushTraceBuffer { - if (traceBuffer) { - @synchronized(traceBuffer) { - for (dispatch_block_t traceBlock in traceBuffer) { - traceBlock(); - } - [traceBuffer removeAllObjects]; - traceBuffer = nil; - MSLogVerbose([MSAppCenter logTag], @"Stop buffering traces, flushed."); - } - } -} - -#pragma mark - Testing - -+ (void)reset { - [self.delegates removeAllObjects]; - [self.originalImplementations removeAllObjects]; - [self.selectorsToSwizzle removeAllObjects]; -} - -@end diff --git a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h b/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h deleted file mode 100644 index 5c3dd0d9df..0000000000 --- a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateForwarderPrivate.h +++ /dev/null @@ -1,57 +0,0 @@ -#import "MSAppDelegateForwarder.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface MSAppDelegateForwarder () - -/** - * Hash table containing all the delegates as weak references. - */ -@property(nonatomic, class) NSHashTable> *delegates; - -/** - * Keep track of original selectors to swizzle. - */ -@property(nonatomic, class, readonly) NSMutableSet *selectorsToSwizzle; - -/** - * Dictionary of deprecated original selectors indexed by their new equivalent. - */ -@property(nonatomic, class, readonly) NSDictionary *deprecatedSelectors; - -/** - * Keep track of the original delegate's method implementations. - */ -@property(nonatomic, class, readonly) NSMutableDictionary *originalImplementations; - -#if TARGET_OS_OSX -/** - * Hold the original @see NSApplication#setDelegate: implementation. - */ -#else -/** - * Hold the original @see UIApplication#setDelegate: implementation. - */ -#endif -@property(nonatomic, class) IMP originalSetDelegateImp; - -/** - * Returns the singleton instance of MSAppDelegateForwarder. - */ -+ (instancetype)sharedInstance; - -/** - * Register swizzling for the given original application delegate. - * - * @param originalDelegate The original application delegate. - */ -+ (void)swizzleOriginalDelegate:(id)originalDelegate; - -/** - * Reset the app delegate forwarder, used for testing only. - */ -+ (void)reset; - -@end - -NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/Channel/MSChannelDelegate.h b/AppCenter/AppCenter/Internals/Channel/MSChannelDelegate.h index a20fe92aa2..1a1293c77f 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSChannelDelegate.h +++ b/AppCenter/AppCenter/Internals/Channel/MSChannelDelegate.h @@ -1,5 +1,7 @@ #import +#import "MSConstants+Flags.h" + @protocol MSChannelUnitProtocol; @protocol MSChannelGroupProtocol; @protocol MSChannelProtocol; @@ -29,16 +31,17 @@ * * @param log The log. * @param internalId An internal Id to keep track of logs. + * @param flags Options for the log. */ -- (void)channel:(id)channel didPrepareLog:(id)log withInternalId:(NSString *)internalId; +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags; /** - * A callback that is called after a log completed the enqueueing process weither it was successfull or not. + * A callback that is called after a log completed the enqueueing process whether it was successful or not. * * @param log The log. * @param internalId An internal Id to keep track of logs. */ -- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log withInternalId:(NSString *)internalId; +- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId; /** * Callback method that will be called before each log will be send to the server. diff --git a/AppCenter/AppCenter/Internals/Channel/MSChannelGroupDefault.m b/AppCenter/AppCenter/Internals/Channel/MSChannelGroupDefault.m index 11a6492a0b..4f057a1062 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSChannelGroupDefault.m +++ b/AppCenter/AppCenter/Internals/Channel/MSChannelGroupDefault.m @@ -1,6 +1,6 @@ +#import "MSChannelGroupDefault.h" #import "AppCenter+Internal.h" #import "MSAppCenterIngestion.h" -#import "MSChannelGroupDefault.h" #import "MSChannelGroupDefaultPrivate.h" #import "MSChannelUnitConfiguration.h" #import "MSChannelUnitDefault.h" @@ -100,17 +100,17 @@ - (void)channel:(id)channel prepareLog:(id)log { }]; } -- (void)channel:(id)channel didPrepareLog:(id)log withInternalId:(NSString *)internalId { - [self enumerateDelegatesForSelector:@selector(channel:didPrepareLog:withInternalId:) +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags { + [self enumerateDelegatesForSelector:@selector(channel:didPrepareLog:internalId:flags:) withBlock:^(id delegate) { - [delegate channel:channel didPrepareLog:log withInternalId:internalId]; + [delegate channel:channel didPrepareLog:log internalId:internalId flags:flags]; }]; } -- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log withInternalId:(NSString *)internalId { - [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:withInternalId:) +- (void)channel:(id)channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId { + [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:internalId:) withBlock:^(id delegate) { - [delegate channel:channel didCompleteEnqueueingLog:log withInternalId:internalId]; + [delegate channel:channel didCompleteEnqueueingLog:log internalId:internalId]; }]; } diff --git a/AppCenter/AppCenter/Internals/Channel/MSChannelGroupProtocol.h b/AppCenter/AppCenter/Internals/Channel/MSChannelGroupProtocol.h index 7e5629ed03..8e6dc42c60 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSChannelGroupProtocol.h +++ b/AppCenter/AppCenter/Internals/Channel/MSChannelGroupProtocol.h @@ -54,8 +54,8 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion The default maximum database size is 10485760 bytes (10 MiB). * - * @param sizeInBytes Maximum size of in bytes. This will be rounded up to the nearest multiple of 4096. Values below 20480 (20 KiB) will be - * ignored. + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20480 bytes (20 KiB) will be ignored. * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size * is successful, and `NO` otherwise. */ diff --git a/AppCenter/AppCenter/Internals/Channel/MSChannelUnitDefault.m b/AppCenter/AppCenter/Internals/Channel/MSChannelUnitDefault.m index fbb84a7617..7b40357c89 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSChannelUnitDefault.m +++ b/AppCenter/AppCenter/Internals/Channel/MSChannelUnitDefault.m @@ -1,9 +1,9 @@ +#import "MSChannelUnitDefault.h" #import "MSAbstractLogInternal.h" #import "MSAppCenterErrors.h" #import "MSAppCenterIngestion.h" #import "MSAppCenterInternal.h" #import "MSChannelUnitConfiguration.h" -#import "MSChannelUnitDefault.h" #import "MSChannelUnitDefaultPrivate.h" #import "MSDeviceTracker.h" #import "MSIngestionProtocol.h" @@ -90,7 +90,7 @@ - (void)ingestionDidReceiveFatalError:(__unused id)ingestio #pragma mark - Managing queue -- (void)enqueueItem:(id)item { +- (void)enqueueItem:(id)item flags:(MSFlags)flags { /* * Set common log info. @@ -118,14 +118,13 @@ - (void)enqueueItem:(id)item { NSString *internalLogId = MS_UUID_STRING; // Notify delegate about enqueuing as fast as possible on the current thread. - [self enumerateDelegatesForSelector:@selector(channel:didPrepareLog:withInternalId:) + [self enumerateDelegatesForSelector:@selector(channel:didPrepareLog:internalId:flags:) withBlock:^(id delegate) { - [delegate channel:self didPrepareLog:item withInternalId:internalLogId]; + [delegate channel:self didPrepareLog:item internalId:internalLogId flags:flags]; }]; // Return fast in case our item is empty or we are discarding logs right now. dispatch_async(self.logsDispatchQueue, ^{ - // Use separate autorelease pool for enqueuing logs. @autoreleasepool { @@ -137,18 +136,18 @@ - (void)enqueueItem:(id)item { }]; if (shouldFilter) { MSLogDebug([MSAppCenter logTag], @"Log of type '%@' was filtered out by delegate(s)", item.type); - [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:withInternalId:) + [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:internalId:) withBlock:^(id delegate) { - [delegate channel:self didCompleteEnqueueingLog:item withInternalId:internalLogId]; + [delegate channel:self didCompleteEnqueueingLog:item internalId:internalLogId]; }]; return; } if (!self.ingestion.isReadyToSend) { MSLogDebug([MSAppCenter logTag], @"Log of type '%@' was not filtered out by delegate(s) but ingestion is not ready to send it.", item.type); - [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:withInternalId:) + [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:internalId:) withBlock:^(id delegate) { - [delegate channel:self didCompleteEnqueueingLog:item withInternalId:internalLogId]; + [delegate channel:self didCompleteEnqueueingLog:item internalId:internalLogId]; }]; return; } @@ -158,22 +157,28 @@ - (void)enqueueItem:(id)item { code:kMSACConnectionPausedErrorCode userInfo:@{NSLocalizedDescriptionKey : kMSACConnectionPausedErrorDesc}]; [self notifyFailureBeforeSendingForItem:item withError:error]; - [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:withInternalId:) + [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:internalId:) withBlock:^(id delegate) { - [delegate channel:self didCompleteEnqueueingLog:item withInternalId:internalLogId]; + [delegate channel:self didCompleteEnqueueingLog:item internalId:internalLogId]; }]; return; } // Save the log first. - MSLogDebug([MSAppCenter logTag], @"Saving log, type: %@.", item.type); - [self.storage saveLog:item withGroupId:self.configuration.groupId]; - self.itemsCount += 1; - [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:withInternalId:) + MSLogDebug([MSAppCenter logTag], @"Saving log, type: %@, flags: %u.", item.type, (unsigned int)flags); + bool success = [self.storage saveLog:item withGroupId:self.configuration.groupId flags:flags]; + + // Notify delegates of completion (whatever the result is). + [self enumerateDelegatesForSelector:@selector(channel:didCompleteEnqueueingLog:internalId:) withBlock:^(id delegate) { - [delegate channel:self didCompleteEnqueueingLog:item withInternalId:internalLogId]; + [delegate channel:self didCompleteEnqueueingLog:item internalId:internalLogId]; }]; - [self checkPendingLogs]; + + // If successful, check if logs can be sent now. + if (success) { + self.itemsCount += 1; + [self checkPendingLogs]; + } } }); } @@ -218,7 +223,6 @@ - (void)flushQueue { limit:self.configuration.batchSizeLimit excludedTargetKeys:[self.pausedTargetKeys allObjects] completionHandler:^(NSArray *_Nonnull logArray, NSString *batchId) { - // Logs may be deleted from storage before this flush. if (batchId.length > 0) { [self.pendingBatchIds addObject:batchId]; diff --git a/AppCenter/AppCenter/Internals/Channel/MSChannelUnitProtocol.h b/AppCenter/AppCenter/Internals/Channel/MSChannelUnitProtocol.h index e2cf5bc170..34141ca055 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSChannelUnitProtocol.h +++ b/AppCenter/AppCenter/Internals/Channel/MSChannelUnitProtocol.h @@ -1,6 +1,7 @@ #import #import "MSChannelProtocol.h" +#import "MSConstants+Flags.h" NS_ASSUME_NONNULL_BEGIN @@ -27,8 +28,9 @@ NS_ASSUME_NONNULL_BEGIN * Enqueue a new log item. * * @param item The log item that should be enqueued. + * @param flags Options for the item being enqueued. */ -- (void)enqueueItem:(id)item; +- (void)enqueueItem:(id)item flags:(MSFlags)flags; /** * Pause sending logs with the given transmission target token. diff --git a/AppCenter/AppCenter/Internals/Channel/MSOneCollectorChannelDelegate.m b/AppCenter/AppCenter/Internals/Channel/MSOneCollectorChannelDelegate.m index aafcd56e32..16957539f4 100644 --- a/AppCenter/AppCenter/Internals/Channel/MSOneCollectorChannelDelegate.m +++ b/AppCenter/AppCenter/Internals/Channel/MSOneCollectorChannelDelegate.m @@ -1,11 +1,11 @@ #import "MSAbstractLogInternal.h" #import "MSAppCenterInternal.h" -#import "MSChannelUnitConfiguration.h" -#import "MSChannelUnitProtocol.h" -#import "MSConstants+Internal.h" #import "MSCSData.h" #import "MSCSEpochAndSeq.h" #import "MSCSExtensions.h" +#import "MSChannelUnitConfiguration.h" +#import "MSChannelUnitProtocol.h" +#import "MSConstants+Internal.h" #import "MSOneCollectorChannelDelegatePrivate.h" #import "MSOneCollectorIngestion.h" #import "MSSDKExtension.h" @@ -46,8 +46,8 @@ - (void)channelGroup:(id)channelGroup didAddChannelUnit: NSString *oneCollectorGroupId = [NSString stringWithFormat:@"%@%@", channel.configuration.groupId, kMSOneCollectorGroupIdSuffix]; MSChannelUnitConfiguration *channelUnitConfiguration = [[MSChannelUnitConfiguration alloc] initDefaultConfigurationWithGroupId:oneCollectorGroupId]; - id channelUnit = - [channelGroup addChannelUnitWithConfiguration:channelUnitConfiguration withIngestion:self.oneCollectorIngestion]; + id channelUnit = [channelGroup addChannelUnitWithConfiguration:channelUnitConfiguration + withIngestion:self.oneCollectorIngestion]; self.oneCollectorChannels[groupId] = channelUnit; } } @@ -73,7 +73,10 @@ - (void)channel:(id)__unused channel prepareLog:(id)lo } } -- (void)channel:(id)channel didPrepareLog:(id)log withInternalId:(NSString *)__unused internalId { +- (void)channel:(id)channel + didPrepareLog:(id)log + internalId:(NSString *)__unused internalId + flags:(MSFlags)flags { id channelUnit = (id)channel; id oneCollectorChannelUnit = nil; NSString *groupId = channelUnit.configuration.groupId; @@ -85,9 +88,7 @@ - (void)channel:(id)channel didPrepareLog:(id)log with if ([(NSObject *)log isKindOfClass:[MSCommonSchemaLog class]] && ![self isOneCollectorGroup:groupId]) { oneCollectorChannelUnit = self.oneCollectorChannels[groupId]; if (oneCollectorChannelUnit) { - dispatch_async(oneCollectorChannelUnit.logsDispatchQueue, ^{ - [oneCollectorChannelUnit enqueueItem:log]; - }); + [oneCollectorChannelUnit enqueueItem:log flags:flags]; } return; } @@ -99,11 +100,9 @@ - (void)channel:(id)channel didPrepareLog:(id)log with return; } id logConversion = (id)log; - NSArray *commonSchemaLogs = [logConversion toCommonSchemaLogs]; + NSArray *commonSchemaLogs = [logConversion toCommonSchemaLogsWithFlags:flags]; for (MSCommonSchemaLog *commonSchemaLog in commonSchemaLogs) { - dispatch_async(oneCollectorChannelUnit.logsDispatchQueue, ^{ - [oneCollectorChannelUnit enqueueItem:commonSchemaLog]; - }); + [oneCollectorChannelUnit enqueueItem:commonSchemaLog flags:flags]; } } diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.h new file mode 100644 index 0000000000..70a8a17e8b --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.h @@ -0,0 +1,12 @@ +#import "MSCustomApplicationDelegate.h" +#import "MSDelegateForwarder.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const kMSAppDelegateForwarderEnabledKey = @"AppCenterAppDelegateForwarderEnabled"; + +@interface MSAppDelegateForwarder : MSDelegateForwarder + +@end + +NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.m b/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.m new file mode 100644 index 0000000000..a5e3e8e98e --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateForwarder.m @@ -0,0 +1,121 @@ +#import "MSAppDelegateForwarder.h" +#import "MSCustomApplicationDelegate.h" +#import "MSUtility+Application.h" + +// Original selectors with special handling. +static NSString *const kMSOpenURLSourceApplicationAnnotation = @"application:openURL:sourceApplication:annotation:"; +static NSString *const kMSOpenURLOptions = @"application:openURL:options:"; + +// Singleton instance. +static MSAppDelegateForwarder *sharedInstance = nil; +static dispatch_once_t swizzlingOnceToken; + +@implementation MSAppDelegateForwarder + ++ (void)load { + + /* + * The application starts querying its delegate for its implementation as soon as it is set then may never query again. It means that if + * the application delegate doesn't implement an optional method of the `UIApplicationDelegate` protocol at that time then that method may + * never be called even if added later via swizzling. This is why the application delegate swizzling should happen at the time it is set + * to the application object. + */ + [[self sharedInstance] setEnabledFromPlistForKey:kMSAppDelegateForwarderEnabledKey]; +} + +- (instancetype)init { + if ((self = [super init])) { +#if !TARGET_OS_OSX + self.deprecatedSelectors = @{kMSOpenURLOptions : kMSOpenURLSourceApplicationAnnotation}; +#endif + } + return self; +} + ++ (instancetype)sharedInstance { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [self new]; + }); + return sharedInstance; +} + ++ (void)resetSharedInstance { + sharedInstance = [self new]; +} + +- (Class)originalClassForSetDelegate { + return [MSApplication class]; +} + +- (dispatch_once_t *)swizzlingOnceToken { + return &swizzlingOnceToken; +} + +#pragma mark - Custom Application + +- (void)custom_setDelegate:(id)delegate { + + // Swizzle only once. + static dispatch_once_t delegateSwizzleOnceToken; + dispatch_once(&delegateSwizzleOnceToken, ^{ + // Swizzle the delegate object before it's actually set. + [[MSAppDelegateForwarder sharedInstance] swizzleOriginalDelegate:delegate]; + }); + + // Forward to the original `setDelegate:` implementation. + IMP originalImp = [MSAppDelegateForwarder sharedInstance].originalSetDelegateImp; + if (originalImp) { + ((void (*)(id, SEL, id))originalImp)(self, _cmd, delegate); + } +} + +#pragma mark - Custom UIApplicationDelegate + +#if !TARGET_OS_OSX + +/* + * Those methods will never get called but their implementation will be used by swizzling. Those implementations will run within the + * delegate context. Meaning that `self` will point to the original app delegate and not this forwarder. + */ +- (BOOL)custom_application:(UIApplication *)application + openURL:(NSURL *)url + sourceApplication:(nullable NSString *)sourceApplication + annotation:(id)annotation { + BOOL result = NO; + IMP originalImp = NULL; + + // Forward to the original delegate. + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + if (originalImp) { + result = ((BOOL(*)(id, SEL, UIApplication *, NSURL *, NSString *, id))originalImp)(self, _cmd, application, url, sourceApplication, + annotation); + } + + // Forward to custom delegates. + return [[MSAppDelegateForwarder sharedInstance] application:application + openURL:url + sourceApplication:sourceApplication + annotation:annotation + returnedValue:result]; +} + +- (BOOL)custom_application:(UIApplication *)application + openURL:(nonnull NSURL *)url + options:(nonnull NSDictionary *)options { + BOOL result = NO; + IMP originalImp = NULL; + + // Forward to the original delegate. + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + if (originalImp) { + result = ((BOOL(*)(id, SEL, UIApplication *, NSURL *, NSDictionary *))originalImp)( + self, _cmd, application, url, options); + } + + // Forward to custom delegates. + return [[MSAppDelegateForwarder sharedInstance] application:application openURL:url options:options returnedValue:result]; +} +#endif + +@end diff --git a/AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateUtil.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateUtil.h similarity index 100% rename from AppCenter/AppCenter/Internals/AppDelegate/MSAppDelegateUtil.h rename to AppCenter/AppCenter/Internals/DelegateForwarder/MSAppDelegateUtil.h diff --git a/AppCenter/AppCenter/Internals/AppDelegate/MSCustomApplicationDelegate.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomApplicationDelegate.h similarity index 92% rename from AppCenter/AppCenter/Internals/AppDelegate/MSCustomApplicationDelegate.h rename to AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomApplicationDelegate.h index 9a012f8fd4..5329de1be1 100644 --- a/AppCenter/AppCenter/Internals/AppDelegate/MSCustomApplicationDelegate.h +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomApplicationDelegate.h @@ -1,15 +1,16 @@ #import "MSAppDelegateUtil.h" +#import "MSCustomDelegate.h" NS_ASSUME_NONNULL_BEGIN /** - * Custom delegate matching `UIApplicationDelegate`. + * Custom delegate matching @c UIApplicationDelegate. * * @discussion Delegates here are using swizzling. Any delegate that can be registered through the notification center should not be * registered through swizzling. Due to the early registration of swizzling on the original app delegate each custom delegate must sign up - * for selectors to swizzle within the `load` method of a category over the @see MSAppDelegateForwarder class. + * for selectors to swizzle within the `load` method of a category over the @c MSAppDelegateForwarder class. */ -@protocol MSCustomApplicationDelegate +@protocol MSCustomApplicationDelegate @optional diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomDelegate.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomDelegate.h new file mode 100644 index 0000000000..adeec1f6c1 --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSCustomDelegate.h @@ -0,0 +1,13 @@ +NS_ASSUME_NONNULL_BEGIN + +/** + * Custom delegate. + * + * @discussion Delegates here are using swizzling. Any delegate that can be registered through the notification center should not be + * registered through swizzling. Due to the early registration of swizzling on the original app delegate each custom delegate must sign up + * for selectors to swizzle within the `load` method of a category over the @see MSDelegateForwarder class. + */ +@protocol MSCustomDelegate +@end + +NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.h new file mode 100644 index 0000000000..aa0f65ee5f --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.h @@ -0,0 +1,99 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol MSCustomDelegate; + +/** + * Enum used to represent all kind of executors running a completion handler. + */ +typedef NS_OPTIONS(NSUInteger, MSCompletionExecutor) { + MSCompletionExecutorNone = (1 << 0), + MSCompletionExecutorOriginal = (1 << 1), + MSCompletionExecutorCustom = (1 << 2), + MSCompletionExecutorForwarder = (1 << 3) +}; + +@interface MSDelegateForwarder : NSObject + +/** + * Enable/Disable Application forwarding. + */ +@property(nonatomic) BOOL enabled; + +/** + * Hash table containing all the delegates as weak references. + */ +@property(nonatomic) NSHashTable> *delegates; + +/** + * Hold the original setDelegate implementation. + */ +@property(nonatomic) IMP originalSetDelegateImp; + +// TODO SEL can be stored as NSValue in dictionaries for a better efficiency. +/** + * Keep track of the original delegate's method implementations. + */ +@property(nonatomic, readonly) NSMutableDictionary *originalImplementations; + +/** + * Dictionary of deprecated original selectors indexed by their new equivalent. + */ +@property(nonatomic) NSDictionary *deprecatedSelectors; + +/** + * Return the singleton instance of a delegate forwarder. + * + * @return The delegate forwarder instance. + * + * @discussion This method is abstract and needs to be overwritten by subclasses. + */ ++ (instancetype)sharedInstance; + +/** + * Register swizzling for the given original application delegate. + * + * @param originalDelegate The original application delegate. + */ +- (void)swizzleOriginalDelegate:(NSObject *)originalDelegate; + +/** + * Add a delegate. This method is thread safe. + * + * @param delegate A delegate. + */ +- (void)addDelegate:(id)delegate; + +/** + * Remove a delegate. This method is thread safe. + * + * @param delegate A delegate. + */ +- (void)removeDelegate:(id)delegate; + +/** + * Add an app delegate selector to swizzle. + * + * @param selector An app delegate selector to swizzle. + * + * @discussion Due to the early registration of swizzling on the original app delegate each custom delegate must sign up for selectors to + * swizzle within the @c load method of a category over the @see MSAppDelegateForwarder class. + */ +- (void)addDelegateSelectorToSwizzle:(SEL)selector; + +/** + * Flush debugging traces accumulated until now. + */ ++ (void)flushTraceBuffer; + +/** + * Set the enabled state from the application plist file. + * + * @param plistKey Plist key for the forwarder enabled state. + */ +- (void)setEnabledFromPlistForKey:(NSString *)plistKey; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.m b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.m new file mode 100644 index 0000000000..209298bd42 --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarder.m @@ -0,0 +1,291 @@ +#import + +#import "MSAppCenterInternal.h" +#import "MSCustomDelegate.h" +#import "MSDelegateForwarder.h" +#import "MSDelegateForwarderPrivate.h" +#import "MSLogger.h" + +static NSString *const kMSCustomSelectorPrefix = @"custom_"; +static NSString *const kMSReturnedValueSelectorPart = @"returnedValue:"; + +// A buffer containing all the console logs that couldn't be printed yet. +static NSMutableArray *traceBuffer = nil; + +@implementation MSDelegateForwarder + ++ (void)load { + traceBuffer = [NSMutableArray new]; +} + +- (instancetype)init { + if ((self = [super init])) { + _delegates = [NSHashTable weakObjectsHashTable]; + _selectorsToSwizzle = [NSMutableSet new]; + _originalImplementations = [NSMutableDictionary new]; + _enabled = YES; + } + return self; +} + ++ (instancetype)sharedInstance { + + // This is an empty method expected to be overridden in sub classes. + return nil; +} + ++ (void)resetSharedInstance { + + // This is an empty method expected to be overridden in sub classes. +} + ++ (NSString *)enabledKey { + + // This is an empty method expected to be overridden in sub classes. + return nil; +} + +- (Class)originalClassForSetDelegate { + + // This is an empty method expected to be overridden in sub classes. + return nil; +} + +- (dispatch_once_t *)swizzlingOnceToken { + + // This is an empty method expected to be overridden in sub classes. + return nil; +} + +#pragma mark - Custom Application + +/** + * Custom implementation of the setDelegate: method. + * + * @param delegate The delegate to be swizzled, its type here is @c id to be generic but your implementation will have to declare + * the exact type of the expected delegate (i.e.: @c MSApplicationDelegate). + * + * @discussion Beware, @c self in this method is not the current class but the swizzled class. + */ +- (void)custom_setDelegate:(__unused id)delegate { + + // This is an empty method expected to be overridden in sub classes. +} + +#pragma mark - Logging + +- (void)addTraceBlock:(void (^)(void))block { + @synchronized(traceBuffer) { + if (traceBuffer) { + static dispatch_once_t onceToken = 0; + dispatch_once(&onceToken, ^{ + [traceBuffer addObject:^{ + MSLogVerbose([MSAppCenter logTag], @"Start buffering traces."); + }]; + }); + [traceBuffer addObject:block]; + } else { + block(); + } + } +} + ++ (void)flushTraceBuffer { + if (traceBuffer) { + @synchronized(traceBuffer) { + for (dispatch_block_t traceBlock in traceBuffer) { + traceBlock(); + } + [traceBuffer removeAllObjects]; + traceBuffer = nil; + MSLogVerbose([MSAppCenter logTag], @"Stop buffering traces, flushed."); + } + } +} + +#pragma mark - Swizzling + +- (void)addDelegateSelectorToSwizzle:(SEL)selector { + if (self.enabled) { + + // Swizzle only once and only if needed. No selector to swizzle then no swizzling at all. + dispatch_once([self swizzlingOnceToken], ^{ + self.originalSetDelegateImp = [self swizzleOriginalSelector:@selector(setDelegate:) + withCustomSelector:@selector(custom_setDelegate:) + originalClass:[self originalClassForSetDelegate]]; + }); + [self.selectorsToSwizzle addObject:NSStringFromSelector(selector)]; + } +} + +- (void)swizzleOriginalDelegate:(NSObject *)originalDelegate { + IMP originalImp = NULL; + Class delegateClass = [originalDelegate class]; + SEL originalSelector, customSelector; + + // Swizzle all registered selectors. + for (NSString *selectorString in self.selectorsToSwizzle) { + originalSelector = NSSelectorFromString(selectorString); + customSelector = NSSelectorFromString([kMSCustomSelectorPrefix stringByAppendingString:selectorString]); + originalImp = [self swizzleOriginalSelector:originalSelector withCustomSelector:customSelector originalClass:delegateClass]; + if (originalImp) { + + // Save the original implementation for later use. + self.originalImplementations[selectorString] = [NSValue valueWithBytes:&originalImp objCType:@encode(IMP)]; + } + } + [self.selectorsToSwizzle removeAllObjects]; +} + +- (IMP)swizzleOriginalSelector:(SEL)originalSelector withCustomSelector:(SEL)customSelector originalClass:(Class)originalClass { + + // Replace original implementation + NSString *originalSelectorStr = NSStringFromSelector(originalSelector); + Method originalMethod = class_getInstanceMethod(originalClass, originalSelector); + IMP customImp = class_getMethodImplementation([self class], customSelector); + IMP originalImp = NULL; + BOOL methodAdded = NO; + BOOL skipped = NO; + NSString *warningMsg; + NSString *remediationMsg = @"You need to explicitly call the App Center API from your app delegate implementation."; + + // Replace original implementation by the custom one. + if (originalMethod) { + originalImp = method_setImplementation(originalMethod, customImp); + } else if (![originalClass instancesRespondToSelector:originalSelector]) { + + // Check for deprecation. + NSString *deprecatedSelectorStr = self.deprecatedSelectors[originalSelectorStr]; + if (deprecatedSelectorStr && [originalClass instancesRespondToSelector:NSSelectorFromString(deprecatedSelectorStr)]) { + + // An implementation for the deprecated selector exists. Don't add the new method, it might eclipse the original implementation. + warningMsg = [NSString + stringWithFormat:@"No implementation found for this selector, though an implementation of its deprecated API '%@' exists.", + deprecatedSelectorStr]; + } else { + + // Skip this selector if it's deprecated and doesn't have an implementation. + if ([self.deprecatedSelectors.allValues containsObject:originalSelectorStr]) { + skipped = YES; + } else { + + /* + * The original class may not implement the selector (e.g.: optional method from protocol), add the method to the original class and + * associate it with the custom implementation. + */ + Method customMethod = class_getInstanceMethod([self class], customSelector); + methodAdded = class_addMethod(originalClass, originalSelector, customImp, method_getTypeEncoding(customMethod)); + } + } + } + + /* + * If class instances respond to the selector but no implementation is found it's likely that the original class is doing message + * forwarding, in this case we can't add our implementation to the class or we will break the forwarding. + */ + + // Validate swizzling. + if (!skipped) { + if (!originalImp && !methodAdded) { + [self addTraceBlock:^{ + NSString *message = [NSString stringWithFormat:@"Cannot swizzle selector '%@' of class '%@'.", originalSelectorStr, originalClass]; + if (warningMsg) { + MSLogWarning([MSAppCenter logTag], @"%@ %@", message, warningMsg); + } else { + MSLogError([MSAppCenter logTag], @"%@ %@", message, remediationMsg); + } + }]; + } else { + [self addTraceBlock:^{ + MSLogDebug([MSAppCenter logTag], @"Selector '%@' of class '%@' is swizzled.", originalSelectorStr, originalClass); + }]; + } + } + return originalImp; +} + +#pragma mark - Forwarding + +- (void)forwardInvocation:(NSInvocation *)invocation { + @synchronized([self class]) { + BOOL forwarded = NO; + BOOL hasReturnedValue = ([NSStringFromSelector(invocation.selector) hasSuffix:kMSReturnedValueSelectorPart]); + NSUInteger returnedValueIdx = 0; + void *returnedValuePtr = NULL; + + // Prepare returned value if any. + if (hasReturnedValue) { + + // Returned value argument is always the last one. + returnedValueIdx = invocation.methodSignature.numberOfArguments - 1; + returnedValuePtr = malloc(invocation.methodSignature.methodReturnLength); + } + + // Forward to delegates executing a custom method. + for (id delegate in self.delegates) { + if ([delegate respondsToSelector:invocation.selector]) { + [invocation invokeWithTarget:delegate]; + + // Chaining return values. + if (hasReturnedValue) { + [invocation getReturnValue:returnedValuePtr]; + [invocation setArgument:returnedValuePtr atIndex:returnedValueIdx]; + } + forwarded = YES; + } + } + + // Forward back the original return value if no delegates to receive the message. + if (hasReturnedValue && !forwarded) { + [invocation getArgument:returnedValuePtr atIndex:returnedValueIdx]; + [invocation setReturnValue:returnedValuePtr]; + } + free(returnedValuePtr); + } +} + +#pragma mark - Delegates + +- (void)addDelegate:(id)delegate { + @synchronized(self) { + if (self.enabled) { + [self.delegates addObject:delegate]; + } + } +} + +- (void)removeDelegate:(id)delegate { + @synchronized(self) { + if (self.enabled) { + [self.delegates removeObject:delegate]; + } + } +} + +#pragma mark - Other + +- (void)setEnabledFromPlistForKey:(NSString *)plistKey { + NSNumber *forwarderEnabledNum = [NSBundle.mainBundle objectForInfoDictionaryKey:plistKey]; + BOOL forwarderEnabled = forwarderEnabledNum ? [forwarderEnabledNum boolValue] : YES; + self.enabled = forwarderEnabled; + if (self.enabled) { + [self addTraceBlock:^{ + MSLogDebug([MSAppCenter logTag], @"Delegate forwarder for info.plist key '%@' enabled. It may use swizzling.", plistKey); + }]; + } else { + [self addTraceBlock:^{ + MSLogDebug([MSAppCenter logTag], @"Delegate forwarder for info.plist key '%@' disabled. It won't use swizzling.", plistKey); + }]; + } +} + +- (void)setEnabled:(BOOL)enabled { + @synchronized(self) { + _enabled = enabled; + if (!enabled) { + [self.delegates removeAllObjects]; + } + } +} + +@end diff --git a/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarderPrivate.h b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarderPrivate.h new file mode 100644 index 0000000000..cfdcb39370 --- /dev/null +++ b/AppCenter/AppCenter/Internals/DelegateForwarder/MSDelegateForwarderPrivate.h @@ -0,0 +1,19 @@ +#import "MSDelegateForwarder.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MSDelegateForwarder () + +/** + * Keep track of original selectors to swizzle. + */ +@property(nonatomic, readonly) NSMutableSet *selectorsToSwizzle; + +/** + * Only used by tests to reset the singleton instance. + */ ++ (void)resetSharedInstance; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/Ingestion/MSHttpIngestion.m b/AppCenter/AppCenter/Internals/Ingestion/MSHttpIngestion.m index f8a6328ac5..0713f0320d 100644 --- a/AppCenter/AppCenter/Internals/Ingestion/MSHttpIngestion.m +++ b/AppCenter/AppCenter/Internals/Ingestion/MSHttpIngestion.m @@ -314,7 +314,7 @@ - (void)setBaseURL:(NSString *)baseURL { // Update full URL. if (components.URL) { - self.sendURL = (NSURL * _Nonnull)components.URL; + self.sendURL = (NSURL * _Nonnull) components.URL; success = true; } } @@ -337,7 +337,7 @@ - (void)networkStateChanged { } /** - * This is an empty method and expect to be overridden in sub classes. + * This is an empty method expected to be overridden in sub classes. */ - (NSURLRequest *)createRequest:(NSObject *)__unused data { return nil; diff --git a/AppCenter/AppCenter/Internals/MSCustomPropertiesInternal.h b/AppCenter/AppCenter/Internals/MSCustomPropertiesInternal.h new file mode 100644 index 0000000000..d2a7b10193 --- /dev/null +++ b/AppCenter/AppCenter/Internals/MSCustomPropertiesInternal.h @@ -0,0 +1,15 @@ +#import "MSCustomProperties.h" + +/** + * Private declarations for MSCustomProperties. + */ +@interface MSCustomProperties () + +/** + * Create an immutable copy of the properties dictionary to use in synchronized scenarios. + * + * @return An immutable copy of properties. + */ +- (NSDictionary *)propertiesImmutableCopy; + +@end diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSAppExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSAppExtension.m index bc42d809b3..2e54b0fc38 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSAppExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSAppExtension.m @@ -19,7 +19,7 @@ - (NSMutableDictionary *)serializeToDictionary { if (self.locale) { dict[kMSAppLocale] = self.locale; } - return dict; + return dict.count == 0 ? nil : dict; } #pragma mark - MSModel diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.h b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.h index a27fcefaac..727a4bdfdf 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.h +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.h @@ -4,7 +4,7 @@ #import "MSSerializableObject.h" /** - * The metadata section contains additional typing/schema-related information for each field in the Part B or Part C payload. + * The data object contains Part B and Part C properties. */ @interface MSCSData : NSObject diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.m index 06991fcbcd..240e48d384 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSData.m @@ -1,13 +1,13 @@ #import "MSCSData.h" -#import "MSCSModelConstants.h" @implementation MSCSData #pragma mark - MSSerializableObject - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [NSMutableDictionary new]; + NSMutableDictionary *dict; if (self.properties) { + dict = [NSMutableDictionary new]; [dict addEntriesFromDictionary:self.properties]; } return dict; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.h b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.h index a355a8c2fb..8a3fa5b699 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.h +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.h @@ -6,6 +6,7 @@ @class MSAppExtension; @class MSDeviceExtension; @class MSLocExtension; +@class MSMetadataExtension; @class MSNetExtension; @class MSOSExtension; @class MSProtocolExtension; @@ -17,6 +18,11 @@ */ @interface MSCSExtensions : NSObject +/** + * The Metadata extension. + */ +@property(nonatomic) MSMetadataExtension *metadataExt; + /** * The Protocol extension. */ diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.m index 5511cfa835..ca26cc6a6b 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSExtensions.m @@ -1,8 +1,9 @@ -#import "MSAppExtension.h" #import "MSCSExtensions.h" +#import "MSAppExtension.h" #import "MSCSModelConstants.h" #import "MSDeviceExtension.h" #import "MSLocExtension.h" +#import "MSMetadataExtension.h" #import "MSNetExtension.h" #import "MSOSExtension.h" #import "MSProtocolExtension.h" @@ -15,6 +16,9 @@ @implementation MSCSExtensions - (NSMutableDictionary *)serializeToDictionary { NSMutableDictionary *dict = [NSMutableDictionary new]; + if (self.metadataExt) { + dict[kMSCSMetadataExt] = [self.metadataExt serializeToDictionary]; + } if (self.protocolExt) { dict[kMSCSProtocolExt] = [self.protocolExt serializeToDictionary]; } @@ -45,10 +49,10 @@ - (NSMutableDictionary *)serializeToDictionary { #pragma mark - MSModel - (BOOL)isValid { - return (!self.protocolExt || [self.protocolExt isValid]) && (!self.userExt || [self.userExt isValid]) && - (!self.deviceExt || [self.deviceExt isValid]) && (!self.osExt || [self.osExt isValid]) && - (!self.appExt || [self.appExt isValid]) && (!self.netExt || [self.netExt isValid]) && (!self.sdkExt || [self.sdkExt isValid]) && - (!self.locExt || [self.locExt isValid]); + return (!self.metadataExt || [self.metadataExt isValid]) && (!self.protocolExt || [self.protocolExt isValid]) && + (!self.userExt || [self.userExt isValid]) && (!self.deviceExt || [self.deviceExt isValid]) && + (!self.osExt || [self.osExt isValid]) && (!self.appExt || [self.appExt isValid]) && (!self.netExt || [self.netExt isValid]) && + (!self.sdkExt || [self.sdkExt isValid]) && (!self.locExt || [self.locExt isValid]); } #pragma mark - NSObject @@ -59,6 +63,7 @@ - (BOOL)isEqual:(id)object { } MSCSExtensions *csExt = (MSCSExtensions *)object; return ((!self.protocolExt && !csExt.protocolExt) || [self.protocolExt isEqual:csExt.protocolExt]) && + ((!self.metadataExt && !csExt.metadataExt) || [self.metadataExt isEqual:csExt.metadataExt]) && ((!self.userExt && !csExt.userExt) || [self.userExt isEqual:csExt.userExt]) && ((!self.deviceExt && !csExt.deviceExt) || [self.deviceExt isEqual:csExt.deviceExt]) && ((!self.osExt && !csExt.osExt) || [self.osExt isEqual:csExt.osExt]) && @@ -72,6 +77,7 @@ - (BOOL)isEqual:(id)object { - (instancetype)initWithCoder:(NSCoder *)coder { if ((self = [super init])) { + _metadataExt = [coder decodeObjectForKey:kMSCSMetadataExt]; _protocolExt = [coder decodeObjectForKey:kMSCSProtocolExt]; _userExt = [coder decodeObjectForKey:kMSCSUserExt]; _deviceExt = [coder decodeObjectForKey:kMSCSDeviceExt]; @@ -85,8 +91,7 @@ - (instancetype)initWithCoder:(NSCoder *)coder { } - (void)encodeWithCoder:(NSCoder *)coder { - - // Fields must be encoded in the following order. + [coder encodeObject:self.metadataExt forKey:kMSCSMetadataExt]; [coder encodeObject:self.protocolExt forKey:kMSCSProtocolExt]; [coder encodeObject:self.userExt forKey:kMSCSUserExt]; [coder encodeObject:self.deviceExt forKey:kMSCSDeviceExt]; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.h b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.h index 406c374d35..acf5c6fefa 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.h +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.h @@ -16,6 +16,10 @@ extern NSString *const kMSCSExt; // Values extern NSString *const kMSCSVerValue; +#pragma mark - MSMetadataExtension + +extern NSString *const kMSFieldDelimiter; + #pragma mark - MSUserExtension extern NSString *const kMSUserLocale; @@ -64,6 +68,7 @@ extern NSString *const kMSDataBaseData; #pragma mark - MSCSExtensions +extern NSString *const kMSCSMetadataExt; extern NSString *const kMSCSProtocolExt; extern NSString *const kMSCSUserExt; extern NSString *const kMSCSDeviceExt; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.m index db897ea04b..396ea38b62 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCSModelConstants.m @@ -16,6 +16,10 @@ // Values NSString *const kMSCSVerValue = @"3.0"; +#pragma mark - MSMetadataExtension + +NSString *const kMSFieldDelimiter = @"f"; + #pragma mark - MSUserExtension NSString *const kMSUserLocale = @"locale"; @@ -64,6 +68,7 @@ #pragma mark - MSCSExtensions +NSString *const kMSCSMetadataExt = @"metadata"; NSString *const kMSCSProtocolExt = @"protocol"; NSString *const kMSCSUserExt = @"user"; NSString *const kMSCSDeviceExt = @"device"; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.h b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.h index f0f80f6a16..16d3fadf50 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.h +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.h @@ -32,7 +32,7 @@ @property(nonatomic, copy) NSString *iKey; /** - * Event Property flags contain a collection of bits that describe how the event should be processed by the Asimov pipeline. + * Event Property flags contain a collection of bits that describe how the event should be processed by the One Collector pipeline. */ @property(nonatomic) int64_t flags; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.m index 2cea78ea5e..389c24250a 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSCommonSchemaLog.m @@ -25,10 +25,13 @@ - (NSMutableDictionary *)serializeToDictionary { dict[kMSCSTime] = [MSUtility dateToISO8601:self.timestamp]; } - // TODO: Not supporting popSample, flags and cV today. + // TODO: Not supporting popSample and cV today. if (self.iKey) { dict[kMSCSIKey] = self.iKey; } + if (self.flags) { + dict[kMSCSFlags] = @(self.flags); + } if (self.ext) { dict[kMSCSExt] = [self.ext serializeToDictionary]; } diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSDeviceExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSDeviceExtension.m index 29b8478ff2..9dd94ae28a 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSDeviceExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSDeviceExtension.m @@ -1,13 +1,14 @@ -#import "MSCSModelConstants.h" #import "MSDeviceExtension.h" +#import "MSCSModelConstants.h" @implementation MSDeviceExtension #pragma mark - MSSerializableObject - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [NSMutableDictionary new]; + NSMutableDictionary *dict; if (self.localId) { + dict = [NSMutableDictionary new]; dict[kMSDeviceLocalId] = self.localId; } return dict; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSLocExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSLocExtension.m index 36a80b15d1..bf6a22130e 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSLocExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSLocExtension.m @@ -1,13 +1,14 @@ -#import "MSCSModelConstants.h" #import "MSLocExtension.h" +#import "MSCSModelConstants.h" @implementation MSLocExtension #pragma mark - MSSerializableObject - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [NSMutableDictionary new]; + NSMutableDictionary *dict; if (self.tz) { + dict = [NSMutableDictionary new]; dict[kMSTimezone] = self.tz; } return dict; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.h b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.h new file mode 100644 index 0000000000..e259e4e618 --- /dev/null +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.h @@ -0,0 +1,16 @@ +#import + +#import "MSModel.h" +#import "MSSerializableObject.h" + +/** + * The metadata section contains additional typing/schema-related information for each field in the Part B or Part C payload. + */ +@interface MSMetadataExtension : NSObject + +/** + * Additional typing/schema-related information for each field in the Part B or Part C payload. + */ +@property(atomic, copy) NSDictionary *metadata; + +@end diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.m new file mode 100644 index 0000000000..114318f193 --- /dev/null +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSMetadataExtension.m @@ -0,0 +1,47 @@ +#import "MSMetadataExtension.h" + +@implementation MSMetadataExtension + +#pragma mark - MSSerializableObject + +- (NSMutableDictionary *)serializeToDictionary { + NSMutableDictionary *dict; + if (self.metadata) { + dict = [NSMutableDictionary new]; + [dict addEntriesFromDictionary:self.metadata]; + } + return dict; +} + +#pragma mark - MSModel + +- (BOOL)isValid { + + // All attributes are optional. + return YES; +} + +#pragma mark - NSObject + +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSMetadataExtension class]]) { + return NO; + } + MSMetadataExtension *csMetadata = (MSMetadataExtension *)object; + return (!self.metadata && !csMetadata) || [self.metadata isEqualToDictionary:csMetadata.metadata]; +} + +#pragma mark - NSCoding + +- (instancetype)initWithCoder:(NSCoder *)coder { + if ((self = [super init])) { + _metadata = [coder decodeObject]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder { + [coder encodeRootObject:self.metadata]; +} + +@end diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSNetExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSNetExtension.m index 18e0252e45..07ba6567d9 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSNetExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSNetExtension.m @@ -1,13 +1,14 @@ -#import "MSCSModelConstants.h" #import "MSNetExtension.h" +#import "MSCSModelConstants.h" @implementation MSNetExtension #pragma mark - MSSerializableObject - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [NSMutableDictionary new]; + NSMutableDictionary *dict; if (self.provider) { + dict = [NSMutableDictionary new]; dict[kMSNetProvider] = self.provider; } return dict; diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSOSExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSOSExtension.m index 273678b392..e8a5fdb051 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSOSExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSOSExtension.m @@ -1,5 +1,5 @@ -#import "MSCSModelConstants.h" #import "MSOSExtension.h" +#import "MSCSModelConstants.h" @implementation MSOSExtension @@ -13,7 +13,7 @@ - (NSMutableDictionary *)serializeToDictionary { if (self.name) { dict[kMSOSName] = self.name; } - return dict; + return dict.count == 0 ? nil : dict; } #pragma mark - MSModel diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSProtocolExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSProtocolExtension.m index 5da6f97443..671c797dd7 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSProtocolExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSProtocolExtension.m @@ -1,5 +1,5 @@ -#import "MSCSModelConstants.h" #import "MSProtocolExtension.h" +#import "MSCSModelConstants.h" @implementation MSProtocolExtension @@ -16,7 +16,7 @@ - (NSMutableDictionary *)serializeToDictionary { if (self.devModel) { dict[kMSDevModel] = self.devModel; } - return dict; + return dict.count == 0 ? nil : dict; } #pragma mark - MSModel diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSSDKExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSSDKExtension.m index bcc0bd8036..496b3bc9f8 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSSDKExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSSDKExtension.m @@ -1,5 +1,5 @@ -#import "MSCSModelConstants.h" #import "MSSDKExtension.h" +#import "MSCSModelConstants.h" @implementation MSSDKExtension @@ -21,7 +21,7 @@ - (NSMutableDictionary *)serializeToDictionary { if (self.seq) { dict[kMSSDKSeq] = @(self.seq); } - return dict; + return dict.count == 0 ? nil : dict; } #pragma mark - MSModel diff --git a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSUserExtension.m b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSUserExtension.m index 82792cb840..e9b311c3f6 100644 --- a/AppCenter/AppCenter/Internals/Model/CommonSchema/MSUserExtension.m +++ b/AppCenter/AppCenter/Internals/Model/CommonSchema/MSUserExtension.m @@ -1,13 +1,14 @@ -#import "MSCSModelConstants.h" #import "MSUserExtension.h" +#import "MSCSModelConstants.h" @implementation MSUserExtension #pragma mark - MSSerializableObject - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [NSMutableDictionary new]; + NSMutableDictionary *dict; if (self.locale) { + dict = [NSMutableDictionary new]; dict[kMSUserLocale] = self.locale; } return dict; diff --git a/AppCenter/AppCenter/Internals/Model/MSACModelConstants.h b/AppCenter/AppCenter/Internals/Model/MSACModelConstants.h index e7f2506638..f275d21d02 100644 --- a/AppCenter/AppCenter/Internals/Model/MSACModelConstants.h +++ b/AppCenter/AppCenter/Internals/Model/MSACModelConstants.h @@ -35,3 +35,12 @@ extern NSString *const kMSDistributionGroupId; extern NSString *const kMSTimestamp; extern NSString *const kMSDevice; extern NSString *const kMSType; + +#pragma mark - MSAbstractLog + +extern NSString *const kMSBooleanTypedPropertyType; +extern NSString *const kMSDateTimeTypedPropertyType; +extern NSString *const kMSDoubleTypedPropertyType; +extern NSString *const kMSLongTypedPropertyType; +extern NSString *const kMSStringTypedPropertyType; +extern NSString *const kMSTypedPropertyValue; diff --git a/AppCenter/AppCenter/Internals/Model/MSACModelConstants.m b/AppCenter/AppCenter/Internals/Model/MSACModelConstants.m index b7a0d50502..c2862bb1c6 100644 --- a/AppCenter/AppCenter/Internals/Model/MSACModelConstants.m +++ b/AppCenter/AppCenter/Internals/Model/MSACModelConstants.m @@ -35,3 +35,12 @@ NSString *const kMSTimestamp = @"timestamp"; NSString *const kMSDevice = @"device"; NSString *const kMSType = @"type"; + +#pragma mark - MSAbstractLog + +NSString *const kMSBooleanTypedPropertyType = @"boolean"; +NSString *const kMSDateTimeTypedPropertyType = @"dateTime"; +NSString *const kMSDoubleTypedPropertyType = @"double"; +NSString *const kMSLongTypedPropertyType = @"long"; +NSString *const kMSStringTypedPropertyType = @"string"; +NSString *const kMSTypedPropertyValue = @"value"; diff --git a/AppCenter/AppCenter/Internals/Model/MSAbstractLog.m b/AppCenter/AppCenter/Internals/Model/MSAbstractLog.m index 0db4d6ee5b..fb247d7087 100644 --- a/AppCenter/AppCenter/Internals/Model/MSAbstractLog.m +++ b/AppCenter/AppCenter/Internals/Model/MSAbstractLog.m @@ -1,6 +1,6 @@ +#import "MSACModelConstants.h" #import "MSAbstractLogInternal.h" #import "MSAbstractLogPrivate.h" -#import "MSACModelConstants.h" #import "MSAppExtension.h" #import "MSCSExtensions.h" #import "MSCSModelConstants.h" @@ -23,6 +23,7 @@ @implementation MSAbstractLog @synthesize sid = _sid; @synthesize distributionGroupId = _distributionGroupId; @synthesize device = _device; +@synthesize tag = _tag; - (instancetype)init { self = [super init]; @@ -61,7 +62,7 @@ - (BOOL)isEqual:(id)object { return NO; } MSAbstractLog *log = (MSAbstractLog *)object; - return ((!self.type && !log.type) || [self.type isEqualToString:log.type]) && + return ((!self.tag && !log.tag) || [self.tag isEqual:log.tag]) && ((!self.type && !log.type) || [self.type isEqualToString:log.type]) && ((!self.timestamp && !log.timestamp) || [self.timestamp isEqualToDate:log.timestamp]) && ((!self.sid && !log.sid) || [self.sid isEqualToString:log.sid]) && ((!self.distributionGroupId && !log.distributionGroupId) || [self.distributionGroupId isEqualToString:log.distributionGroupId]) && @@ -96,7 +97,6 @@ - (NSString *)serializeLogWithPrettyPrinting:(BOOL)prettyPrint { NSString *jsonString; NSJSONWritingOptions printOptions = prettyPrint ? NSJSONWritingPrettyPrinted : (NSJSONWritingOptions)0; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[self serializeToDictionary] options:printOptions error:nil]; - if (jsonData) { jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\\/" withString:@"/"]; @@ -125,10 +125,10 @@ - (void)addTransmissionTargetToken:(NSString *)token { #pragma mark - MSLogConversion -- (NSArray *)toCommonSchemaLogs { +- (NSArray *)toCommonSchemaLogsWithFlags:(MSFlags)flags { NSMutableArray *csLogs = [NSMutableArray new]; for (NSString *token in self.transmissionTargetTokens) { - MSCommonSchemaLog *csLog = [self toCommonSchemaLogForTargetToken:token]; + MSCommonSchemaLog *csLog = [self toCommonSchemaLogForTargetToken:token flags:(MSFlags)flags]; if (csLog) { [csLogs addObject:csLog]; } @@ -140,7 +140,7 @@ - (void)addTransmissionTargetToken:(NSString *)token { #pragma mark - Helper -- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token { +- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token flags:(MSFlags)flags { MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; csLog.transmissionTargetTokens = [NSSet setWithObject:token]; csLog.ver = kMSCSVerValue; @@ -150,8 +150,8 @@ - (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token { // Calculate iKey based on the target token. csLog.iKey = [MSUtility iKeyFromTargetToken:token]; + csLog.flags = flags; - // TODO flags not supported at this time. // TODO cV not supported at this time. // Setup extensions. diff --git a/AppCenter/AppCenter/Internals/Model/MSAbstractLogInternal.h b/AppCenter/AppCenter/Internals/Model/MSAbstractLogInternal.h index dc6aa92b5c..91fa6f3bcd 100644 --- a/AppCenter/AppCenter/Internals/Model/MSAbstractLogInternal.h +++ b/AppCenter/AppCenter/Internals/Model/MSAbstractLogInternal.h @@ -1,5 +1,6 @@ #import "MSAbstractLog.h" #import "MSCommonSchemaLog.h" +#import "MSConstants.h" #import "MSLog.h" #import "MSLogConversion.h" #import "MSSerializableObject.h" @@ -19,8 +20,10 @@ * Convert an AppCenter log to the Common Schema 3.0 event log per tenant token. * * @param token The tenant token. + * @param flags Flags to set for the common schema log. * * @return A common schema log. */ -- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token; +- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token flags:(MSFlags)flags; + @end diff --git a/AppCenter/AppCenter/Internals/Model/MSLog.h b/AppCenter/AppCenter/Internals/Model/MSLog.h index fb9e9c8b28..480b1f7fab 100644 --- a/AppCenter/AppCenter/Internals/Model/MSLog.h +++ b/AppCenter/AppCenter/Internals/Model/MSLog.h @@ -30,6 +30,12 @@ */ @property(nonatomic) MSDevice *device; +/** + * Transient object tag. For example, a log can be tagged with a transmission target. We do this currently to prevent properties being + * applied retroactively to previous logs by comparing their tags. + */ +@property(nonatomic) NSObject *tag; + /** * Checks if the object's values are valid. * diff --git a/AppCenter/AppCenter/Internals/Model/MSLogConversion.h b/AppCenter/AppCenter/Internals/Model/MSLogConversion.h index ce0be7be4c..71de9a64a7 100644 --- a/AppCenter/AppCenter/Internals/Model/MSLogConversion.h +++ b/AppCenter/AppCenter/Internals/Model/MSLogConversion.h @@ -1,12 +1,19 @@ #import +#import "MSConstants+Flags.h" + @class MSCommonSchemaLog; @protocol MSLogConversion /** - * Keep track of common schema logs. + * Method to transform a log into one or several common schema logs. If the log has multiple transmission target tokens, the conversion will + * produce one log per token. + * + * @param flags The Common Schema flags for the log. + * + * @return An array of MCSCommonSchemaLog objects. */ -- (NSArray *)toCommonSchemaLogs; +- (NSArray *)toCommonSchemaLogsWithFlags:(MSFlags)flags; @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSBooleanTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSBooleanTypedProperty.m index aa6c2a0b43..91fd7dcfae 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSBooleanTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSBooleanTypedProperty.m @@ -1,6 +1,5 @@ #import "MSBooleanTypedProperty.h" - -static NSString *const kMSBooleanTypedPropertyType = @"boolean"; +#import "MSACModelConstants.h" @implementation MSBooleanTypedProperty @@ -30,4 +29,12 @@ - (NSMutableDictionary *)serializeToDictionary { return dict; } +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSBooleanTypedProperty class]] || ![super isEqual:object]) { + return NO; + } + MSBooleanTypedProperty *property = (MSBooleanTypedProperty *)object; + return (self.value == property.value); +} + @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSDateTimeTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSDateTimeTypedProperty.m index 2ecc08612c..4b7fa68cdc 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSDateTimeTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSDateTimeTypedProperty.m @@ -1,8 +1,7 @@ #import "MSDateTimeTypedProperty.h" +#import "MSACModelConstants.h" #import "MSUtility+Date.h" -static NSString *const kMSDateTimeTypedPropertyType = @"dateTime"; - @implementation MSDateTimeTypedProperty - (instancetype)init { @@ -31,4 +30,12 @@ - (NSMutableDictionary *)serializeToDictionary { return dict; } +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSDateTimeTypedProperty class]] || ![super isEqual:object]) { + return NO; + } + MSDateTimeTypedProperty *property = (MSDateTimeTypedProperty *)object; + return ((!self.value && !property.value) || [self.value isEqualToDate:property.value]); +} + @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSDoubleTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSDoubleTypedProperty.m index 1b1ab19503..443165778f 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSDoubleTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSDoubleTypedProperty.m @@ -1,6 +1,5 @@ #import "MSDoubleTypedProperty.h" - -static NSString *const kMSDoubleTypedPropertyType = @"double"; +#import "MSACModelConstants.h" @implementation MSDoubleTypedProperty @@ -30,4 +29,12 @@ - (NSMutableDictionary *)serializeToDictionary { return dict; } +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSDoubleTypedProperty class]] || ![super isEqual:object]) { + return NO; + } + MSDoubleTypedProperty *property = (MSDoubleTypedProperty *)object; + return (self.value == property.value); +} + @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSLongTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSLongTypedProperty.m index 36662f5d30..33d9028367 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSLongTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSLongTypedProperty.m @@ -1,33 +1,40 @@ #import "MSLongTypedProperty.h" - -static NSString *const kMSLongTypedPropertyType = @"long"; +#import "MSACModelConstants.h" @implementation MSLongTypedProperty - (instancetype)init { - if ((self = [super init])) { - self.type = kMSLongTypedPropertyType; - } - return self; + if ((self = [super init])) { + self.type = kMSLongTypedPropertyType; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - _value = [coder decodeInt64ForKey:kMSTypedPropertyValue]; - } - return self; + self = [super initWithCoder:coder]; + if (self) { + _value = [coder decodeInt64ForKey:kMSTypedPropertyValue]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeInt64:self.value forKey:kMSTypedPropertyValue]; + [super encodeWithCoder:coder]; + [coder encodeInt64:self.value forKey:kMSTypedPropertyValue]; } - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary]; - dict[kMSTypedPropertyValue] = @(self.value); - return dict; + NSMutableDictionary *dict = [super serializeToDictionary]; + dict[kMSTypedPropertyValue] = @(self.value); + return dict; +} + +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSLongTypedProperty class]] || ![super isEqual:object]) { + return NO; + } + MSLongTypedProperty *property = (MSLongTypedProperty *)object; + return (self.value == property.value); } @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSStringTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSStringTypedProperty.m index e0cd9bf9c8..83928b6f9f 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSStringTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSStringTypedProperty.m @@ -1,33 +1,40 @@ #import "MSStringTypedProperty.h" +#import "MSACModelConstants.h" @implementation MSStringTypedProperty -static NSString *const kMSStringTypedPropertyType = @"string"; - - (instancetype)init { - if ((self = [super init])) { - self.type = kMSStringTypedPropertyType; - } - return self; + if ((self = [super init])) { + self.type = kMSStringTypedPropertyType; + } + return self; } - (instancetype)initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; - if (self) { - _value = [coder decodeObjectForKey:kMSTypedPropertyValue]; - } - return self; + self = [super initWithCoder:coder]; + if (self) { + _value = [coder decodeObjectForKey:kMSTypedPropertyValue]; + } + return self; } - (void)encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:self.value forKey:kMSTypedPropertyValue]; + [super encodeWithCoder:coder]; + [coder encodeObject:self.value forKey:kMSTypedPropertyValue]; } - (NSMutableDictionary *)serializeToDictionary { - NSMutableDictionary *dict = [super serializeToDictionary]; - dict[kMSTypedPropertyValue] = self.value; - return dict; + NSMutableDictionary *dict = [super serializeToDictionary]; + dict[kMSTypedPropertyValue] = self.value; + return dict; +} + +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSStringTypedProperty class]] || ![super isEqual:object]) { + return NO; + } + MSStringTypedProperty *property = (MSStringTypedProperty *)object; + return ((!self.value && !property.value) || [self.value isEqualToString:property.value]); } @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.h b/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.h index a2acfed090..b212c1acbe 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.h +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.h @@ -2,8 +2,6 @@ #import "MSSerializableObject.h" -static NSString *const kMSTypedPropertyValue = @"value"; - @interface MSTypedProperty : NSObject /** @@ -12,8 +10,8 @@ static NSString *const kMSTypedPropertyValue = @"value"; @property(nonatomic, copy) NSString *type; /** -* Property name. -*/ + * Property name. + */ @property(nonatomic, copy) NSString *name; @end diff --git a/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.m b/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.m index 2a8a62a03a..dbc520fc0d 100644 --- a/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.m +++ b/AppCenter/AppCenter/Internals/Model/Properties/MSTypedProperty.m @@ -28,4 +28,12 @@ - (NSMutableDictionary *)serializeToDictionary { return dict; } +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSTypedProperty class]]) { + return NO; + } + MSTypedProperty *property = (MSTypedProperty *)object; + return ((!self.type && !property.type) || [self.type isEqualToString:property.type]); +} + @end diff --git a/AppCenter/AppCenter/Internals/Storage/MSDBStorage.h b/AppCenter/AppCenter/Internals/Storage/MSDBStorage.h index e8dfb4255c..448c4d0dfd 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSDBStorage.h +++ b/AppCenter/AppCenter/Internals/Storage/MSDBStorage.h @@ -29,11 +29,6 @@ static NSString *const kMSSQLiteConstraintAutoincrement = @"AUTOINCREMENT"; @interface MSDBStorage : NSObject -/** - * Maximum number of pages allowed in the database. - */ -@property(nonatomic) int maxPageCount; - /** * Initialize this database with a schema and a filename for its creation. * @@ -85,8 +80,8 @@ static NSString *const kMSSQLiteConstraintAutoincrement = @"AUTOINCREMENT"; /** * Set the maximum size of the internal storage. This method must be called before App Center is started. * - * @param sizeInBytes Maximum size of in bytes. This will be rounded up to the nearest multiple of 4096. Values below 20,480 (20 KiB) will - * be ignored. + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored. * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size * is successful, and `NO` otherwise. * diff --git a/AppCenter/AppCenter/Internals/Storage/MSDBStorage.m b/AppCenter/AppCenter/Internals/Storage/MSDBStorage.m index 119ad59acd..9a40948e7e 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSDBStorage.m +++ b/AppCenter/AppCenter/Internals/Storage/MSDBStorage.m @@ -9,37 +9,50 @@ @implementation MSDBStorage - (instancetype)initWithSchema:(MSDBSchema *)schema version:(NSUInteger)version filename:(NSString *)filename { if ((self = [super init])) { _dbFileURL = [MSUtility createFileAtPathComponent:filename withData:nil atomically:NO forceOverwrite:NO]; - _maxPageCount = [MSDBStorage numberOfPagesInBytes:kMSDefaultDatabaseSizeInBytes]; + _maxSizeInBytes = kMSDefaultDatabaseSizeInBytes; // If it is custom SQLite library we need to turn on URI filename capability. sqlite3_config(SQLITE_CONFIG_URI, 1); // Execute all initialize operation with one database instance. - [self executeQueryUsingBlock:^int(void *db) { + int result; + sqlite3 *db = [MSDBStorage openDatabaseAtFileURL:self.dbFileURL withResult:&result]; + if (db) { + _pageSize = [MSDBStorage getPageSizeInOpenedDatabase:db]; // Create tables based on schema. NSUInteger tablesCreated = [MSDBStorage createTablesWithSchema:schema inOpenedDatabase:db]; BOOL newDatabase = tablesCreated == schema.count; NSUInteger databaseVersion = [MSDBStorage versionInOpenedDatabase:db]; - if (databaseVersion < version && !newDatabase) { + if (newDatabase) { + MSLogInfo([MSAppCenter logTag], @"Created \"%@\" database with %lu version.", filename, (unsigned long)version); + [self customizeDatabase:db]; + } else if (databaseVersion < version) { MSLogInfo([MSAppCenter logTag], @"Migrate \"%@\" database from %lu to %lu version.", filename, (unsigned long)databaseVersion, (unsigned long)version); [self migrateDatabase:db fromVersion:databaseVersion]; } [MSDBStorage enableAutoVacuumInOpenedDatabase:db]; [MSDBStorage setVersion:version inOpenedDatabase:db]; - return SQLITE_OK; - }]; + sqlite3_close(db); + }; } return self; } - (int)executeQueryUsingBlock:(MSDBStorageQueryBlock)callback { int result; - sqlite3 *db = [self openDatabaseAtFileURL:self.dbFileURL withMaxPageCount:self.maxPageCount withResult:&result]; + sqlite3 *db = [MSDBStorage openDatabaseAtFileURL:self.dbFileURL withResult:&result]; if (!db) { return result; } + + // The value is stored as part of the database connection and must be reset every time the database is opened. + long maxPageCount = self.maxSizeInBytes / self.pageSize; + result = [MSDBStorage setMaxPageCount:maxPageCount inOpenedDatabase:db]; + if (result != SQLITE_OK) { + MSLogError([MSAppCenter logTag], @"Failed to open database with specified maximum size constraint."); + } result = callback(db); sqlite3_close(db); return result; @@ -145,7 +158,10 @@ - (int)executeNonSelectionQuery:(NSString *)query { + (int)executeNonSelectionQuery:(NSString *)query inOpenedDatabase:(void *)db { char *errMsg; int result = sqlite3_exec(db, [query UTF8String], NULL, NULL, &errMsg); - if (result != SQLITE_OK) { + if (result == SQLITE_FULL) { + MSLogDebug([MSAppCenter logTag], @"Query failed with error: %d - %@", result, [[NSString alloc] initWithUTF8String:errMsg]); + } + else if (result != SQLITE_OK) { MSLogError([MSAppCenter logTag], @"Query \"%@\" failed with error: %d - %@", query, result, [[NSString alloc] initWithUTF8String:errMsg]); } @@ -204,78 +220,89 @@ + (int)executeNonSelectionQuery:(NSString *)query inOpenedDatabase:(void *)db { return entries; } +- (void)customizeDatabase:(void *)__unused db { +} + - (void)migrateDatabase:(void *)__unused db fromVersion:(NSUInteger)__unused version { } - (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(nullable void (^)(BOOL))completionHandler { + int result; + BOOL success; + sqlite3 *db = [MSDBStorage openDatabaseAtFileURL:self.dbFileURL withResult:&result]; + if (!db) { + return; + } // Check the current number of pages in the database to determine whether the requested size will shrink the database. - NSArray *rows = [self executeSelectionQuery:@"PRAGMA page_count;"]; - int currentPageCount = [(NSNumber *)rows[0][0] intValue]; - MSLogDebug([MSAppCenter logTag], @"Found %i pages in the database.", currentPageCount); - int requestedMaxPageCount = [MSDBStorage numberOfPagesInBytes:sizeInBytes]; + long currentPageCount = [MSDBStorage getPageCountInOpenedDatabase:db]; + MSLogDebug([MSAppCenter logTag], @"Found %ld pages in the database.", currentPageCount); + long requestedMaxPageCount = sizeInBytes % self.pageSize ? sizeInBytes / self.pageSize + 1 : sizeInBytes / self.pageSize; if (currentPageCount > requestedMaxPageCount) { - MSLogWarning([MSAppCenter logTag], @"Cannot change database size to %ld bytes as it would cause a loss of data. " - "Maximum database size will not be changed.", + MSLogWarning([MSAppCenter logTag], + @"Cannot change database size to %ld bytes as it would cause a loss of data. " + "Maximum database size will not be changed.", sizeInBytes); - if (completionHandler) { - completionHandler(NO); - } - return; - } - - // Attempt to open the database with the given limit and check the page count to make sure the given limit works. - int result; - BOOL success; - sqlite3 *db = [self openDatabaseAtFileURL:self.dbFileURL withMaxPageCount:requestedMaxPageCount withResult:&result]; - if (result != SQLITE_OK) { - MSLogError([MSAppCenter logTag], @"Could not change maximum database size to %ld bytes. SQLite error code: %i", sizeInBytes, result); success = NO; } else { - rows = [MSDBStorage executeSelectionQuery:@"PRAGMA max_page_count;" inOpenedDatabase:db]; - int currentMaxPageCount = [(NSNumber *)rows[0][0] intValue]; - long actualSize = requestedMaxPageCount * kMSDefaultPageSizeInBytes; - if (requestedMaxPageCount != currentMaxPageCount) { - MSLogError([MSAppCenter logTag], @"Could not change maximum database size to %ld bytes, current maximum size is %ld bytes.", - sizeInBytes, actualSize); + + // Attempt to set the limit and check the page count to make sure the given limit works. + result = [MSDBStorage setMaxPageCount:requestedMaxPageCount inOpenedDatabase:db]; + if (result != SQLITE_OK) { + MSLogError([MSAppCenter logTag], @"Could not change maximum database size to %ld bytes. SQLite error code: %i", sizeInBytes, result); success = NO; } else { - if (sizeInBytes == actualSize) { - MSLogInfo([MSAppCenter logTag], @"Changed maximum database size to %ld bytes.", actualSize); + long currentMaxPageCount = [MSDBStorage getMaxPageCountInOpenedDatabase:db]; + long actualMaxSize = currentMaxPageCount * self.pageSize; + if (requestedMaxPageCount != currentMaxPageCount) { + MSLogError([MSAppCenter logTag], @"Could not change maximum database size to %ld bytes, current maximum size is %ld bytes.", + sizeInBytes, actualMaxSize); + success = NO; } else { - MSLogInfo([MSAppCenter logTag], @"Changed maximum database size to %ld bytes (next multiple of 4KiB).", actualSize); + if (sizeInBytes == actualMaxSize) { + MSLogInfo([MSAppCenter logTag], @"Changed maximum database size to %ld bytes.", actualMaxSize); + } else { + MSLogInfo([MSAppCenter logTag], @"Changed maximum database size to %ld bytes (next multiple of 4KiB).", actualMaxSize); + } + self.maxSizeInBytes = actualMaxSize; + success = YES; } - self.maxPageCount = requestedMaxPageCount; - success = YES; } } + sqlite3_close(db); if (completionHandler) { completionHandler(success); } - sqlite3_close(db); } -- (sqlite3 *)openDatabaseAtFileURL:(NSURL *)fileURL withMaxPageCount:(int)maxPageCount withResult:(int *)result { ++ (sqlite3 *)openDatabaseAtFileURL:(NSURL *)fileURL withResult:(int *)result { sqlite3 *db = NULL; *result = sqlite3_open_v2([[fileURL absoluteString] UTF8String], &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); if (*result != SQLITE_OK) { MSLogError([MSAppCenter logTag], @"Failed to open database with result: %d.", *result); return NULL; } - NSString *statement = [NSString stringWithFormat:@"PRAGMA max_page_count = %i;", maxPageCount]; - char *errorMessage; - *result = sqlite3_exec(db, [statement UTF8String], NULL, NULL, &errorMessage); - if (*result != SQLITE_OK) { - errorMessage = errorMessage ? errorMessage : "(nil)"; - NSString *printableErrorMessage = [NSString stringWithCString:errorMessage encoding:NSUTF8StringEncoding]; - MSLogError([MSAppCenter logTag], @"Failed to open database with specified maximum size constraint. Error message: %@", - printableErrorMessage); - } return db; } -+ (int)numberOfPagesInBytes:(long)bytes { - return (int)(ceil((double)bytes / (double)kMSDefaultPageSizeInBytes)); ++ (long)getPageSizeInOpenedDatabase:(void *)db { + NSArray *rows = [MSDBStorage executeSelectionQuery:@"PRAGMA page_size;" inOpenedDatabase:db]; + return [(NSNumber *)rows[0][0] longValue]; +} + ++ (long)getPageCountInOpenedDatabase:(void *)db { + NSArray *rows = [MSDBStorage executeSelectionQuery:@"PRAGMA page_count;" inOpenedDatabase:db]; + return [(NSNumber *)rows[0][0] longValue]; +} + ++ (long)getMaxPageCountInOpenedDatabase:(void *)db { + NSArray *rows = [MSDBStorage executeSelectionQuery:@"PRAGMA max_page_count;" inOpenedDatabase:db]; + return [(NSNumber *)rows[0][0] longValue]; +} + ++ (int)setMaxPageCount:(long)maxPageCount inOpenedDatabase:(void *)db { + NSString *statement = [NSString stringWithFormat:@"PRAGMA max_page_count = %ld;", maxPageCount]; + return [MSDBStorage executeNonSelectionQuery:statement inOpenedDatabase:db]; } @end diff --git a/AppCenter/AppCenter/Internals/Storage/MSDBStoragePrivate.h b/AppCenter/AppCenter/Internals/Storage/MSDBStoragePrivate.h index 1afd112336..c98049df9a 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSDBStoragePrivate.h +++ b/AppCenter/AppCenter/Internals/Storage/MSDBStoragePrivate.h @@ -4,9 +4,6 @@ NS_ASSUME_NONNULL_BEGIN typedef int (^MSDBStorageQueryBlock)(void *); -// 4 KiB. -static const long kMSDefaultPageSizeInBytes = 4 * 1024; - // 10 MiB. static const long kMSDefaultDatabaseSizeInBytes = 10 * 1024 * 1024; @@ -17,6 +14,24 @@ static const long kMSDefaultDatabaseSizeInBytes = 10 * 1024 * 1024; */ @property(nonatomic, readonly, nullable) NSURL *dbFileURL; + +/** + * Maximum size of the database. + */ +@property(nonatomic) long maxSizeInBytes; + +/** + * Page size for database. + */ +@property(nonatomic, readonly) long pageSize; + +/** + * Called after the database is created. Override to customize the database. + * + * @param db Database handle. + */ +- (void)customizeDatabase:(void *)db; + /** * Called when migration is needed. Override to customize. * diff --git a/AppCenter/AppCenter/Internals/Storage/MSLogDBStorage.m b/AppCenter/AppCenter/Internals/Storage/MSLogDBStorage.m index b614ce06cf..5cea94976e 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSLogDBStorage.m +++ b/AppCenter/AppCenter/Internals/Storage/MSLogDBStorage.m @@ -1,12 +1,13 @@ #import #import "MSAppCenterInternal.h" +#import "MSConstants+Internal.h" #import "MSDBStoragePrivate.h" #import "MSLogDBStoragePrivate.h" #import "MSLogDBStorageVersion.h" #import "MSUtility+StringFormatting.h" -static const NSUInteger kMSSchemaVersion = 2; +static const NSUInteger kMSSchemaVersion = 3; @implementation MSLogDBStorage @@ -22,7 +23,7 @@ - (instancetype)init { @{kMSIdColumnName : @[ kMSSQLiteTypeInteger, kMSSQLiteConstraintPrimaryKey, kMSSQLiteConstraintAutoincrement ]}, @{kMSGroupIdColumnName : @[ kMSSQLiteTypeText, kMSSQLiteConstraintNotNull ]}, @{kMSLogColumnName : @[ kMSSQLiteTypeText, kMSSQLiteConstraintNotNull ]}, @{kMSTargetTokenColumnName : @[ kMSSQLiteTypeText ]}, - @{kMSTargetKeyColumnName : @[ kMSSQLiteTypeText ]} + @{kMSTargetKeyColumnName : @[ kMSSQLiteTypeText ]}, @{kMSPriorityColumnName : @[ kMSSQLiteTypeInteger ]} ] }; self = [super initWithSchema:schema version:kMSSchemaVersion filename:kMSDBFileName]; @@ -40,41 +41,74 @@ - (instancetype)init { #pragma mark - Save logs -- (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId { +- (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId flags:(MSFlags)flags { if (!log) { return NO; } + MSFlags persistenceFlags = flags & kMSPersistenceFlagsMask; // Insert this log to the DB. NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\") VALUES ('%@', '%@')", kMSLogTableName, - kMSGroupIdColumnName, kMSLogColumnName, groupId, base64Data]; + NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", \"%@\") VALUES ('%@', '%@', '%u')", + kMSLogTableName, kMSGroupIdColumnName, kMSLogColumnName, kMSPriorityColumnName, + groupId, base64Data, (unsigned int)persistenceFlags]; // Serialize target token. if ([(NSObject *)log isKindOfClass:[MSCommonSchemaLog class]]) { NSString *targetToken = [[log transmissionTargetTokens] anyObject]; NSString *encryptedToken = [self.targetTokenEncrypter encryptString:targetToken]; NSString *targetKey = [MSUtility targetKeyFromTargetToken:targetToken]; - addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", " - @"\"%@\", \"%@\") VALUES ('%@', '%@', '%@', %@)", - kMSLogTableName, kMSGroupIdColumnName, kMSLogColumnName, kMSTargetTokenColumnName, - kMSTargetKeyColumnName, groupId, base64Data, encryptedToken, - targetKey ? [NSString stringWithFormat:@"'%@'", targetKey] : @"NULL"]; + addLogQuery = + [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", " + @"\"%@\", \"%@\", \"%@\") VALUES ('%@', '%@', '%@', %@, '%u')", + kMSLogTableName, kMSGroupIdColumnName, kMSLogColumnName, kMSTargetTokenColumnName, + kMSTargetKeyColumnName, kMSPriorityColumnName, groupId, base64Data, encryptedToken, + targetKey ? [NSString stringWithFormat:@"'%@'", targetKey] : @"NULL", (unsigned int)persistenceFlags]; } - int result = [self executeNonSelectionQuery:addLogQuery]; - - // If the database is full, delete logs until there is room to add the log. - long countOfLogsDeleted = 0; - while (result == SQLITE_FULL) { - [self deleteOldestLogsWithCount:1]; - ++countOfLogsDeleted; - result = [self executeNonSelectionQuery:addLogQuery]; - } - if (countOfLogsDeleted > 0) { - MSLogDebug([MSAppCenter logTag], @"Log storage was over capacity, %ld oldest log(s) deleted.", (long)countOfLogsDeleted); - } - return result == SQLITE_OK; + return [self executeQueryUsingBlock:^int(void *db) { + int result = [MSDBStorage executeNonSelectionQuery:addLogQuery inOpenedDatabase:db]; + NSMutableArray *logsCanBeDeleted = nil; + if (result == SQLITE_FULL) { + + // Selecting logs with equal or lower priority and ordering by priority then age. + NSString *query = [NSString stringWithFormat:@"SELECT \"%@\" FROM \"%@\" WHERE \"%@\" <= %u ORDER BY \"%@\" ASC, \"%@\" ASC", + kMSIdColumnName, kMSLogTableName, kMSPriorityColumnName, (unsigned int)flags, + kMSPriorityColumnName, kMSIdColumnName]; + NSArray *entries = [MSDBStorage executeSelectionQuery:query inOpenedDatabase:db]; + logsCanBeDeleted = [NSMutableArray new]; + for (NSMutableArray *row in entries) { + [logsCanBeDeleted addObject:row[0]]; + } + } + + // If the database is full, delete logs until there is room to add the log. + long countOfLogsDeleted = 0; + NSUInteger index = 0; + while (result == SQLITE_FULL && index < [logsCanBeDeleted count]) { + result = [MSLogDBStorage deleteLogsFromDBWithColumnValues:@[ logsCanBeDeleted[index] ] + columnName:kMSIdColumnName + inOpenedDatabase:db]; + if (result != SQLITE_OK) { + break; + } + MSLogDebug([MSAppCenter logTag], @"Deleted a log with id %@ to store a new log.", logsCanBeDeleted[index]); + ++countOfLogsDeleted; + ++index; + result = [MSDBStorage executeNonSelectionQuery:addLogQuery inOpenedDatabase:db]; + } + if (countOfLogsDeleted > 0) { + MSLogDebug([MSAppCenter logTag], @"Log storage was over capacity, %ld oldest log(s) with equal or lower priority deleted.", + (long)countOfLogsDeleted); + } + if (result == SQLITE_OK) { + MSLogVerbose([MSAppCenter logTag], @"Log is stored with id: '%ld'", (long)sqlite3_last_insert_rowid(db)); + } else if (result == SQLITE_FULL && index == [logsCanBeDeleted count]) { + MSLogDebug([MSAppCenter logTag], + @"No logs with equal or lower priority found and the storage is already full; discarding the log."); + } + return result; + }] == SQLITE_OK; } #pragma mark - Load logs @@ -94,11 +128,11 @@ - (BOOL)loadLogsWithGroupId:(NSString *)groupId NSMutableArray *idsInBatches = [NSMutableArray new]; for (NSString *batchKey in [self.batches allKeys]) { if ([batchKey hasPrefix:groupId]) { - [idsInBatches addObjectsFromArray:(NSArray * _Nonnull)self.batches[batchKey]]; + [idsInBatches addObjectsFromArray:(NSArray * _Nonnull) self.batches[batchKey]]; } } - // Build the "WHERE" clause's condition. + // Build the "WHERE" clause's conditions. NSMutableString *condition = [NSMutableString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, groupId]; // Filter out paused target keys. @@ -111,6 +145,9 @@ - (BOOL)loadLogsWithGroupId:(NSString *)groupId [condition appendFormat:@" AND \"%@\" NOT IN (%@)", kMSIdColumnName, [idsInBatches componentsJoinedByString:@", "]]; } + // Build the "ORDER BY" clause's conditions. + [condition appendFormat:@" ORDER BY \"%@\" DESC, \"%@\" ASC", kMSPriorityColumnName, kMSIdColumnName]; + /* * There is a need to determine if there will be more logs available than those under the limit. This is just about knowing if there is at * least 1 log above the limit. @@ -140,6 +177,7 @@ - (BOOL)loadLogsWithGroupId:(NSString *)groupId if (logsAvailable) { batchId = MS_UUID_STRING; self.batches[[groupId stringByAppendingString:batchId]] = dbIds; + MSLogVerbose([MSAppCenter logTag], @"Load log(s) with id(s) '%@' as batch Id:%@", [dbIds componentsJoinedByString:@"','"], batchId); } // Load completed. @@ -208,8 +246,8 @@ - (void)deleteLogsWithBatchId:(NSString *)batchId groupId:(NSString *)groupId { // Get logs from DB. for (NSMutableArray *row in entries) { NSNumber *dbId = row[self.idColumnIndex]; - NSData *logData = - [[NSData alloc] initWithBase64EncodedString:row[self.logColumnIndex] options:NSDataBase64DecodingIgnoreUnknownCharacters]; + NSData *logData = [[NSData alloc] initWithBase64EncodedString:row[self.logColumnIndex] + options:NSDataBase64DecodingIgnoreUnknownCharacters]; id log; NSException *exception; @@ -253,6 +291,12 @@ - (void)deleteLogsFromDBWithColumnValue:(id)columnValue columnName:(NSString *)c } - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues columnName:(NSString *)columnName { + [self executeQueryUsingBlock:^int(void *db) { + return [MSLogDBStorage deleteLogsFromDBWithColumnValues:columnValues columnName:columnName inOpenedDatabase:db]; + }]; +} + ++ (int)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues columnName:(NSString *)columnName inOpenedDatabase:(void *)db { NSString *deletionTrace = [NSString stringWithFormat:@"Deletion of log(s) by %@ with value(s) '%@'", columnName, [columnValues componentsJoinedByString:@"','"]]; @@ -264,17 +308,13 @@ - (void)deleteLogsFromDBWithColumnValues:(NSArray *)columnValues columnName:(NSS NSString *deleteLogsQuery = [NSString stringWithFormat:@"DELETE FROM \"%@\" WHERE %@", kMSLogTableName, whereCondition]; // Execute. - if ([self executeNonSelectionQuery:deleteLogsQuery] == SQLITE_OK) { + int result = [MSDBStorage executeNonSelectionQuery:deleteLogsQuery inOpenedDatabase:db]; + if (result == SQLITE_OK) { MSLogVerbose([MSAppCenter logTag], @"%@ %@", deletionTrace, @"succeeded."); } else { MSLogError([MSAppCenter logTag], @"%@ %@", deletionTrace, @"failed."); } -} - -- (void)deleteOldestLogsWithCount:(NSInteger)count { - NSString *deleteLogQuery = - [NSString stringWithFormat:@"DELETE FROM \"%@\" ORDER BY \"%@\" ASC LIMIT %ld", kMSLogTableName, kMSIdColumnName, (long)count]; - [self executeNonSelectionQuery:deleteLogQuery]; + return result; } #pragma mark - DB count @@ -285,22 +325,39 @@ - (NSUInteger)countLogs { #pragma mark - DB migration +- (void)createPriorityIndex:(void *)db { + NSString *indexStatement = [NSString stringWithFormat:@"CREATE INDEX \"ix_%@_%@\" ON \"%@\" (\"%@\")", kMSLogTableName, + kMSPriorityColumnName, kMSLogTableName, kMSPriorityColumnName]; + [MSDBStorage executeNonSelectionQuery:indexStatement inOpenedDatabase:db]; +} + +- (void)customizeDatabase:(void *)db { + [self createPriorityIndex:db]; +} + /* * Migration process is implemented through database versioning. * After altering current schema, database version should be bumped and actions for migration should be implemented in this method. */ - (void)migrateDatabase:(void *)db fromVersion:(NSUInteger)version { if (version < kMSTargetTokenVersion) { - NSString *migrationQuery = [NSString stringWithFormat:@"ALTER TABLE \"%@\" ADD COLUMN \"%@\" " - "TEXT", - kMSLogTableName, kMSTargetTokenColumnName]; + NSString *migrationQuery = [NSString + stringWithFormat:@"ALTER TABLE \"%@\" ADD COLUMN \"%@\" %@", kMSLogTableName, kMSTargetTokenColumnName, kMSSQLiteTypeText]; [MSDBStorage executeNonSelectionQuery:migrationQuery inOpenedDatabase:db]; } if (version < kMSTargetKeyVersion) { - NSString *migrationQuery = [NSString stringWithFormat:@"ALTER TABLE \"%@\" ADD COLUMN \"%@\" " - "TEXT", - kMSLogTableName, kMSTargetKeyColumnName]; + NSString *migrationQuery = + [NSString stringWithFormat:@"ALTER TABLE \"%@\" ADD COLUMN \"%@\" %@", kMSLogTableName, kMSTargetKeyColumnName, kMSSQLiteTypeText]; + [MSDBStorage executeNonSelectionQuery:migrationQuery inOpenedDatabase:db]; + } + if (version < kMSLogPersistencePriorityVersion) { + + // Integer type for flags is actually unsigned int, but SQL resolves UNSIGNED INTEGER to INTEGER anyways. + NSString *migrationQuery = + [NSString stringWithFormat:@"ALTER TABLE \"%@\" ADD COLUMN \"%@\" %@ DEFAULT %u", kMSLogTableName, kMSPriorityColumnName, + kMSSQLiteTypeInteger, (unsigned int)MSFlagsPersistenceNormal]; [MSDBStorage executeNonSelectionQuery:migrationQuery inOpenedDatabase:db]; + [self createPriorityIndex:db]; } } diff --git a/AppCenter/AppCenter/Internals/Storage/MSLogDBStoragePrivate.h b/AppCenter/AppCenter/Internals/Storage/MSLogDBStoragePrivate.h index 583f48cf56..c18cd8d906 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSLogDBStoragePrivate.h +++ b/AppCenter/AppCenter/Internals/Storage/MSLogDBStoragePrivate.h @@ -3,7 +3,6 @@ NS_ASSUME_NONNULL_BEGIN -static NSString *const kMSLogEntityName = @"MSDBLog"; static NSString *const kMSDBFileName = @"Logs.sqlite"; static NSString *const kMSLogTableName = @"logs"; static NSString *const kMSIdColumnName = @"id"; @@ -11,6 +10,7 @@ static NSString *const kMSGroupIdColumnName = @"groupId"; static NSString *const kMSLogColumnName = @"log"; static NSString *const kMSTargetTokenColumnName = @"targetToken"; static NSString *const kMSTargetKeyColumnName = @"targetKey"; +static NSString *const kMSPriorityColumnName = @"priority"; @protocol MSDatabaseConnection; diff --git a/AppCenter/AppCenter/Internals/Storage/MSLogDBStorageVersion.h b/AppCenter/AppCenter/Internals/Storage/MSLogDBStorageVersion.h index cb482c3784..10502a11af 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSLogDBStorageVersion.h +++ b/AppCenter/AppCenter/Internals/Storage/MSLogDBStorageVersion.h @@ -3,5 +3,6 @@ NS_ASSUME_NONNULL_BEGIN static NSUInteger const kMSInitialVersion = 0; static NSUInteger const kMSTargetTokenVersion = 1; static NSUInteger const kMSTargetKeyVersion = 2; +static NSUInteger const kMSLogPersistencePriorityVersion = 3; NS_ASSUME_NONNULL_END diff --git a/AppCenter/AppCenter/Internals/Storage/MSStorage.h b/AppCenter/AppCenter/Internals/Storage/MSStorage.h index 63a65153bc..0d7679455b 100644 --- a/AppCenter/AppCenter/Internals/Storage/MSStorage.h +++ b/AppCenter/AppCenter/Internals/Storage/MSStorage.h @@ -1,5 +1,6 @@ #import +#import "MSConstants+Flags.h" #import "MSLog.h" #import "MSLogContainer.h" @@ -25,10 +26,11 @@ typedef void (^MSLoadDataCompletionHandler)(NSArray> *_Nullable logArr * * @param log The log to be stored. * @param groupId The key used for grouping logs. + * @param flags A flag that indicates if the log has critical persistence priority. * * @return BOOL that indicates if the log was saved successfully. */ -- (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId; +- (BOOL)saveLog:(id)log withGroupId:(NSString *)groupId flags:(MSFlags)flags; /** * Get the number of logs stored in the storage. @@ -72,8 +74,8 @@ typedef void (^MSLoadDataCompletionHandler)(NSArray> *_Nullable logArr /** * Set the maximum size of the internal storage. This method must be called before App Center is started. * - * @param sizeInBytes Maximum size of in bytes. This will be rounded up to the nearest multiple of 4096. Values below 20480 (20 KiB) will be - * ignored. + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20480 bytes (20 KiB) will be ignored. * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size * is successful, and `NO` otherwise. * diff --git a/AppCenter/AppCenter/Internals/Util/MSConstants+Internal.h b/AppCenter/AppCenter/Internals/Util/MSConstants+Internal.h index 2bb642682b..692b74f4a7 100644 --- a/AppCenter/AppCenter/Internals/Util/MSConstants+Internal.h +++ b/AppCenter/AppCenter/Internals/Util/MSConstants+Internal.h @@ -152,3 +152,8 @@ static const long kMSMaximumCommonSchemaLogSizeInBytes = 2 * 1024 * 1024; * Suffix for One Collector group ID. */ static NSString *const kMSOneCollectorGroupIdSuffix = @"/one"; + +/** + * Bit mask for persistence flags. + */ +static const NSUInteger kMSPersistenceFlagsMask = 0xFF; diff --git a/AppCenter/AppCenter/MSAppCenter.h b/AppCenter/AppCenter/MSAppCenter.h index 99cc8a4cf7..78ac1f8edd 100644 --- a/AppCenter/AppCenter/MSAppCenter.h +++ b/AppCenter/AppCenter/MSAppCenter.h @@ -177,8 +177,8 @@ * Set the maximum size of the internal storage. This method must be called before App Center is started. This method is only intended for * applications. * - * @param sizeInBytes Maximum size in bytes. This will be rounded up to the nearest multiple of 4096. Values below 20,480 (20 KiB) will be - * ignored. + * @param sizeInBytes Maximum size of the internal storage in bytes. This will be rounded up to the nearest multiple of a SQLite page size + * (default is 4096 bytes). Values below 20,480 bytes (20 KiB) will be ignored. * * @param completionHandler Callback that is invoked when the database size has been set. The `BOOL` parameter is `YES` if changing the size * is successful, and `NO` otherwise. This parameter can be null. diff --git a/AppCenter/AppCenter/MSAppCenter.m b/AppCenter/AppCenter/MSAppCenter.m index c87279237a..fbd37d92fd 100644 --- a/AppCenter/AppCenter/MSAppCenter.m +++ b/AppCenter/AppCenter/MSAppCenter.m @@ -15,8 +15,8 @@ #import "MSUtility+StringFormatting.h" #if !TARGET_OS_TV +#import "MSCustomPropertiesInternal.h" #import "MSCustomPropertiesLog.h" -#import "MSCustomPropertiesPrivate.h" #endif /** @@ -122,7 +122,7 @@ + (BOOL)isEnabled { + (BOOL)isAppDelegateForwarderEnabled { @synchronized([MSAppCenter sharedInstance]) { - return MSAppDelegateForwarder.enabled; + return [MSAppDelegateForwarder sharedInstance].enabled; } } @@ -138,7 +138,7 @@ + (void)setLogLevel:(MSLogLevel)logLevel { MSLogger.currentLogLevel = logLevel; // The logger is not set at the time of swizzling but now may be a good time to flush the traces. - [MSAppDelegateForwarder flushTraceBuffer]; + [MSDelegateForwarder flushTraceBuffer]; } + (void)setLogHandler:(MSLogHandler)logHandler { @@ -268,8 +268,9 @@ - (void)start:(NSString *)secretString withServices:(NSArray *)services f @synchronized(self) { NSString *appSecret = [MSUtility appSecretFrom:secretString]; NSString *transmissionTargetToken = [MSUtility transmissionTargetTokenFrom:secretString]; - BOOL configured = - [self configureWithAppSecret:appSecret transmissionTargetToken:transmissionTargetToken fromApplication:fromApplication]; + BOOL configured = [self configureWithAppSecret:appSecret + transmissionTargetToken:transmissionTargetToken + fromApplication:fromApplication]; if (configured && services) { NSArray *sortedServices = [self sortServices:services]; MSLogVerbose([MSAppCenter logTag], @"Start services %@ from %@", [sortedServices componentsJoinedByString:@", "], @@ -443,11 +444,12 @@ - (void)setMaxStorageSize:(long)sizeInBytes completionHandler:(void (^)(BOOL))co #if !TARGET_OS_TV - (void)setCustomProperties:(MSCustomProperties *)customProperties { - if (!customProperties || (customProperties.properties.count == 0)) { + NSDictionary *propertiesCopy = [customProperties propertiesImmutableCopy]; + if (!customProperties || (propertiesCopy.count == 0)) { MSLogError([MSAppCenter logTag], @"Custom properties may not be null or empty"); return; } - [self sendCustomPropertiesLog:customProperties.properties]; + [self sendCustomPropertiesLog:propertiesCopy]; } #endif @@ -576,7 +578,7 @@ - (void)sendStartServiceLog:(NSArray *)servicesNames { if (self.isEnabled) { MSStartServiceLog *serviceLog = [MSStartServiceLog new]; serviceLog.services = servicesNames; - [self.channelUnit enqueueItem:serviceLog]; + [self.channelUnit enqueueItem:serviceLog flags:MSFlagsDefault]; } else { if (self.startedServiceNames == nil) { self.startedServiceNames = [NSMutableArray new]; @@ -589,7 +591,7 @@ - (void)sendStartServiceLog:(NSArray *)servicesNames { - (void)sendCustomPropertiesLog:(NSDictionary *)properties { MSCustomPropertiesLog *customPropertiesLog = [MSCustomPropertiesLog new]; customPropertiesLog.properties = properties; - [self.channelUnit enqueueItem:customPropertiesLog]; + [self.channelUnit enqueueItem:customPropertiesLog flags:MSFlagsDefault]; } #endif diff --git a/AppCenter/AppCenter/MSConstants+Flags.h b/AppCenter/AppCenter/MSConstants+Flags.h new file mode 100644 index 0000000000..38ecb1ae73 --- /dev/null +++ b/AppCenter/AppCenter/MSConstants+Flags.h @@ -0,0 +1,8 @@ +#import + +typedef NS_OPTIONS(NSUInteger, MSFlags) { + MSFlagsNone = (0 << 0), // => 00000000 + MSFlagsPersistenceNormal = (1 << 0), // => 00000001 + MSFlagsPersistenceCritical = (1 << 1), // => 00000010 + MSFlagsDefault = MSFlagsPersistenceNormal +}; diff --git a/AppCenter/AppCenter/MSCustomProperties.m b/AppCenter/AppCenter/MSCustomProperties.m index 3dc7e6c8de..a50822aee3 100644 --- a/AppCenter/AppCenter/MSCustomProperties.m +++ b/AppCenter/AppCenter/MSCustomProperties.m @@ -35,15 +35,19 @@ - (instancetype)setDate:(NSDate *)value forKey:(NSString *)key { } - (instancetype)setObject:(NSObject *)value forKey:(NSString *)key { - if ([self isValidKey:key] && [self isValidValue:value]) { - [self.properties setObject:value forKey:key]; + @synchronized(self.properties) { + if ([self isValidKey:key] && [self isValidValue:value]) { + [self.properties setObject:value forKey:key]; + } } return self; } - (instancetype)clearPropertyForKey:(NSString *)key { - if ([self isValidKey:key]) { - [self.properties setObject:[NSNull null] forKey:key]; + @synchronized(self.properties) { + if ([self isValidKey:key]) { + [self.properties setObject:[NSNull null] forKey:key]; + } } return self; } @@ -84,6 +88,12 @@ - (BOOL)isValidValue:(NSObject *)value { MSLogError([MSAppCenter logTag], @"Custom property value length cannot be longer than \"%d\" characters.", maxPropertyValueLength); return NO; } + } else if ([value isKindOfClass:[NSNumber class]]) { + double number = [(NSNumber *)value doubleValue]; + if (number == (double)INFINITY || number == -(double)INFINITY || number != number) { + MSLogError([MSAppCenter logTag], @"Custom property value cannot be NaN or infinite."); + return NO; + } } } else { MSLogError([MSAppCenter logTag], @"Custom property value cannot be null, did you mean to call clear?"); @@ -92,4 +102,10 @@ - (BOOL)isValidValue:(NSObject *)value { return YES; } +- (NSDictionary *)propertiesImmutableCopy { + @synchronized(self.properties) { + return [[NSDictionary alloc] initWithDictionary:self.properties]; + } +} + @end diff --git a/AppCenter/AppCenter/MSLogger.m b/AppCenter/AppCenter/MSLogger.m index 1250087646..539bac186e 100644 --- a/AppCenter/AppCenter/MSLogger.m +++ b/AppCenter/AppCenter/MSLogger.m @@ -33,8 +33,7 @@ @implementation MSLogger level = @"ASSERT"; break; case MSLogLevelNone: - level = @""; - break; + return; } NSLog(@"[%@] %@: %@/%d %@", tag, level, [NSString stringWithCString:function encoding:NSUTF8StringEncoding], line, messageProvider()); } diff --git a/AppCenter/AppCenterTests/MSAbstractLogTests.m b/AppCenter/AppCenterTests/MSAbstractLogTests.m index f7673f6f59..301fb4c395 100644 --- a/AppCenter/AppCenterTests/MSAbstractLogTests.m +++ b/AppCenter/AppCenterTests/MSAbstractLogTests.m @@ -119,27 +119,37 @@ - (void)testIsEqual { self.sut.sid = sid; self.sut.distributionGroupId = distributionGroupId; self.sut.device = device; + self.sut.tag = [NSObject new]; + MSAbstractLog *log = [MSAbstractLog new]; + log.type = self.sut.type; + log.timestamp = self.sut.timestamp; + log.sid = self.sut.sid; + log.distributionGroupId = self.sut.distributionGroupId; + log.device = self.sut.device; + log.tag = self.sut.tag; + + // Then + XCTAssertTrue([self.sut isEqual:log]); // When - NSData *serializedEvent = [NSKeyedArchiver archivedDataWithRootObject:self.sut]; - id actual = [NSKeyedUnarchiver unarchiveObjectWithData:serializedEvent]; - MSAbstractLog *actualLog = actual; + self.sut.type = @"new-fake"; // Then - XCTAssertTrue([self.sut isEqual:actualLog]); + XCTAssertFalse([self.sut isEqual:log]); // When - self.sut.type = @"new-fake"; + self.sut.tag = [NSObject new]; // Then - XCTAssertFalse([self.sut isEqual:actualLog]); + XCTAssertFalse([self.sut isEqual:log]); // When self.sut.type = @"fake"; self.sut.distributionGroupId = @"FAKE-NEW-GROUP-ID"; + self.sut.tag = [NSObject new]; // Then - XCTAssertFalse([self.sut isEqual:actualLog]); + XCTAssertFalse([self.sut isEqual:log]); } - (void)testSerializingToJsonWorks { @@ -189,7 +199,7 @@ - (void)testNoCommonSchemaLogCreatedWhenNilTargetTokenArray { self.sut.transmissionTargetTokens = nil; // When - NSArray *csLogs = [self.sut toCommonSchemaLogs]; + NSArray *csLogs = [self.sut toCommonSchemaLogsWithFlags:MSFlagsDefault]; // Then XCTAssertNil(csLogs); @@ -201,7 +211,7 @@ - (void)testNoCommonSchemaLogCreatedWhenEmptyTargetTokenArray { self.sut.transmissionTargetTokens = [@[] mutableCopy]; // When - NSArray *csLogs = [self.sut toCommonSchemaLogs]; + NSArray *csLogs = [self.sut toCommonSchemaLogsWithFlags:MSFlagsDefault]; // Then XCTAssertNil(csLogs); @@ -234,9 +244,10 @@ - (void)testCommonSchemaLogsCorrectWhenConverted { NSString *expectedAppLocale = @"fr_DE"; OCMStub([bundleMock mainBundle]).andReturn(bundleMock); OCMStub([bundleMock preferredLocalizations]).andReturn(@[ expectedAppLocale ]); + MSFlags expectedFlags = MSFlagsPersistenceNormal; // When - NSArray *csLogs = [self.sut toCommonSchemaLogs]; + NSArray *csLogs = [self.sut toCommonSchemaLogsWithFlags:MSFlagsPersistenceNormal]; // Then XCTAssertEqual(csLogs.count, expectedTokens.count); @@ -249,6 +260,7 @@ - (void)testCommonSchemaLogsCorrectWhenConverted { XCTAssertEqualObjects(log.ver, @"3.0"); XCTAssertEqualObjects(self.sut.timestamp, log.timestamp); XCTAssertTrue([expectedIKeys containsObject:log.iKey]); + XCTAssertEqual(expectedFlags, log.flags); // Extension. XCTAssertNotNil(log.ext); diff --git a/AppCenter/AppCenterTests/MSAppCenterTests.m b/AppCenter/AppCenterTests/MSAppCenterTests.m index b6eccc6b2f..68e86a69e6 100644 --- a/AppCenter/AppCenterTests/MSAppCenterTests.m +++ b/AppCenter/AppCenterTests/MSAppCenterTests.m @@ -406,11 +406,11 @@ - (void)testSetCustomPropertiesWithEmptyPropertiesDoesNotEnqueueCustomProperties // If [MSAppCenter start:MS_UUID_STRING withServices:nil]; id channelUnit = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); - OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]]]).andDo(nil); + OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]] flags:MSFlagsDefault]).andDo(nil); [MSAppCenter sharedInstance].channelUnit = channelUnit; // When - OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]]]); + OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]] flags:MSFlagsDefault]); MSCustomProperties *customProperties = [MSCustomProperties new]; [MSAppCenter setCustomProperties:customProperties]; @@ -423,7 +423,7 @@ - (void)testSetCustomProperties { // If [MSAppCenter start:MS_UUID_STRING withServices:nil]; id channelUnit = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); - OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]]]).andDo(nil); + OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]] flags:MSFlagsDefault]).andDo(nil); [MSAppCenter sharedInstance].channelUnit = channelUnit; // When @@ -432,11 +432,11 @@ - (void)testSetCustomProperties { [MSAppCenter setCustomProperties:customProperties]; // Then - OCMVerify([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]]]); + OCMVerify([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]] flags:MSFlagsDefault]); // When // Not allow processLog more - OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]]]); + OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSCustomPropertiesLog class]] flags:MSFlagsDefault]); [MSAppCenter setCustomProperties:nil]; [MSAppCenter setCustomProperties:[MSCustomProperties new]]; @@ -492,7 +492,7 @@ - (void)testStartWithoutServices { OCMStub([channelGroup addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnit); // Not allow processLog. - OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]]]); + OCMReject([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]] flags:MSFlagsDefault]); // When [MSAppCenter start:MS_UUID_STRING withServices:nil]; @@ -509,14 +509,14 @@ - (void)testStartServiceLogIsSentAfterStartService { // If [MSAppCenter start:MS_UUID_STRING withServices:nil]; id channelUnit = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); - OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]]]).andDo(nil); + OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]] flags:MSFlagsDefault]).andDo(nil); [MSAppCenter sharedInstance].channelUnit = channelUnit; // When [MSAppCenter startService:MSMockService.class]; // Then - OCMVerify([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]]]); + OCMVerify([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]] flags:MSFlagsDefault]); } - (void)testDisabledCoreStatus { @@ -555,10 +555,11 @@ - (void)testStartServiceLogWithDisabledCore { OCMStub([channelGroup addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnit); __block NSInteger logsProcessed = 0; __block MSStartServiceLog *log = nil; - OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]]]).andDo(^(NSInvocation *invocation) { - [invocation getArgument:&log atIndex:2]; - logsProcessed++; - }); + OCMStub([channelUnit enqueueItem:[OCMArg isKindOfClass:[MSStartServiceLog class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + [invocation getArgument:&log atIndex:2]; + logsProcessed++; + }); // When [MSAppCenter start:MS_UUID_STRING withServices:nil]; @@ -677,7 +678,6 @@ - (void)testSetStorageHandlerCannotBeCalledAfterStart { // When [MSAppCenter setMaxStorageSize:dbSize completionHandler:^(BOOL success) { - // Then XCTAssertFalse(success); }]; @@ -708,7 +708,6 @@ - (void)testSetStorageSizeBelowMaximumLogSizeFails { // When [MSAppCenter setMaxStorageSize:10 completionHandler:^(BOOL success) { - // Then XCTAssertFalse(success); [expectation fulfill]; diff --git a/AppCenter/AppCenterTests/MSAppDelegateForwarderTests.m b/AppCenter/AppCenterTests/MSAppDelegateForwarderTests.m index a2d7969f06..a0292f0659 100644 --- a/AppCenter/AppCenterTests/MSAppDelegateForwarderTests.m +++ b/AppCenter/AppCenterTests/MSAppDelegateForwarderTests.m @@ -1,13 +1,16 @@ #import -#import "MSAppDelegateForwarderPrivate.h" +#import "MSAppDelegateForwarder.h" #import "MSAppDelegateUtil.h" +#import "MSDelegateForwarderPrivate.h" +#import "MSDelegateForwarderTestUtil.h" #import "MSTestFrameworks.h" #import "MSUtility+Application.h" @interface MSAppDelegateForwarderTest : XCTestCase @property(nonatomic) MSApplication *appMock; +@property(nonatomic) MSAppDelegateForwarder *sut; @end @@ -30,41 +33,84 @@ - (void)setUp { [super setUp]; // The app delegate forwarder is already set via the load method, reset it for testing. - [MSAppDelegateForwarder reset]; + [MSAppDelegateForwarder resetSharedInstance]; + self.sut = [MSAppDelegateForwarder sharedInstance]; // Mock app delegate. self.appMock = OCMClassMock([MSApplication class]); } - (void)tearDown { - [MSAppDelegateForwarder reset]; [super tearDown]; + [MSAppDelegateForwarder resetSharedInstance]; +} + +- (void)testSetEnabledYesFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSAppDelegateForwarderEnabledKey]).andReturn(@YES); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isTrue()); +} + +- (void)testSetEnabledNoFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSAppDelegateForwarderEnabledKey]).andReturn(@NO); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isFalse()); +} + +- (void)testSetEnabledNoneFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSAppDelegateForwarderEnabledKey]).andReturn(nil); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isTrue()); } - (void)testAddAppDelegateSelectorToSwizzle { // If - NSUInteger currentCount = MSAppDelegateForwarder.selectorsToSwizzle.count; + NSUInteger currentCount = self.sut.selectorsToSwizzle.count; SEL expectedSelector = @selector(testAddAppDelegateSelectorToSwizzle); NSString *expectedSelectorStr = NSStringFromSelector(expectedSelector); // Then - assertThatBool([MSAppDelegateForwarder.selectorsToSwizzle containsObject:expectedSelectorStr], isFalse()); + assertThatBool([self.sut.selectorsToSwizzle containsObject:expectedSelectorStr], isFalse()); // When - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:expectedSelector]; + [self.sut addDelegateSelectorToSwizzle:expectedSelector]; // Then - assertThatInteger(MSAppDelegateForwarder.selectorsToSwizzle.count, equalToUnsignedInteger(currentCount + 1)); - assertThatBool([MSAppDelegateForwarder.selectorsToSwizzle containsObject:expectedSelectorStr], isTrue()); + assertThatInteger(self.sut.selectorsToSwizzle.count, equalToUnsignedInteger(currentCount + 1)); + assertThatBool([self.sut.selectorsToSwizzle containsObject:expectedSelectorStr], isTrue()); // When - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:expectedSelector]; + [self.sut addDelegateSelectorToSwizzle:expectedSelector]; // Then - assertThatInteger(MSAppDelegateForwarder.selectorsToSwizzle.count, equalToUnsignedInteger(currentCount + 1)); - assertThatBool([MSAppDelegateForwarder.selectorsToSwizzle containsObject:expectedSelectorStr], isTrue()); - [MSAppDelegateForwarder.selectorsToSwizzle removeObject:expectedSelectorStr]; + assertThatInteger(self.sut.selectorsToSwizzle.count, equalToUnsignedInteger(currentCount + 1)); + assertThatBool([self.sut.selectorsToSwizzle containsObject:expectedSelectorStr], isTrue()); + [self.sut.selectorsToSwizzle removeObject:expectedSelectorStr]; } #if !TARGET_OS_OSX @@ -76,17 +122,17 @@ - (void)testSwizzleOriginalOpenURLDelegate { // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; NSDictionary *expectedOptions = @{}; // App delegate not implementing any selector. id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL selectorToSwizzle = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -101,11 +147,11 @@ - (void)testSwizzleOriginalOpenURLDelegate { wasCalled = YES; return YES; }; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -116,13 +162,14 @@ - (void)testSwizzleOriginalOpenURLDelegate { // If // App delegate implementing the selector indirectly. id originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; - originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [MSDelegateForwarderTestUtil createInstanceWithBaseClass:[originalBaseAppDelegate class] + andConformItToProtocol:nil]; wasCalled = NO; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -138,13 +185,14 @@ - (void)testSwizzleOriginalOpenURLDelegate { baseWasCalled = YES; }; originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; - [self addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; - originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [MSDelegateForwarderTestUtil createInstanceWithBaseClass:[originalBaseAppDelegate class] + andConformItToProtocol:nil]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -162,13 +210,13 @@ - (void)testSwizzleOriginalOpenURLDelegate { }; // Adding a class method to a class requires its meta class. - [self addSelector:instancesRespondToSelector - implementation:instancesRespondToSelectorImp - toClass:object_getClass([originalAppDelegate class])]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:instancesRespondToSelector + implementation:instancesRespondToSelectorImp + toClass:object_getClass([originalAppDelegate class])]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // Then // Original delegate still responding to selector. @@ -187,7 +235,7 @@ - (void)testForwardUnknownSelector { // When @try { - [[MSAppDelegateForwarder new] performSelector:@selector(testForwardUnknownSelector)]; + [self.sut performSelector:@selector(testForwardUnknownSelector)]; } @catch (NSException *ex) { // Then @@ -208,10 +256,9 @@ - (void)testWithoutCustomDelegate { MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalOpenURLSel = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLSel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLSel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -219,13 +266,13 @@ - (void)testWithoutCustomDelegate { [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:originalOpenURLSel implementation:originalOpenURLImp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLSel implementation:originalOpenURLImp toInstance:originalAppDelegate]; // When BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then - assertThatUnsignedLong(MSAppDelegateForwarder.delegates.count, equalToUnsignedLong(0)); + assertThatUnsignedLong(self.sut.delegates.count, equalToUnsignedLong(0)); assertThatBool(returnedValue, is(@(expectedReturnedValue))); [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; } @@ -237,27 +284,26 @@ - (void)testWithoutCustomDelegateNotReturningValue { NSData *expectedToken = [@"Device token" dataUsingEncoding:NSUTF8StringEncoding]; MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; - SEL originalDidRegisterForRemoteNotificationsWithDeviceTokenSel = - @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel]; + SEL originalDidRegisterForRemoteNotificationsWithDeviceTokenSel = @selector(application: + didRegisterForRemoteNotificationsWithDeviceToken:); + [self.sut addDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalDidRegisterForRemoteNotificationsWithDeviceTokenImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - [self addSelector:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel - implementation:originalDidRegisterForRemoteNotificationsWithDeviceTokenImp - toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalDidRegisterForRemoteNotificationsWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationsWithDeviceTokenImp + toInstance:originalAppDelegate]; // When [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then - assertThatUnsignedLong(MSAppDelegateForwarder.delegates.count, equalToUnsignedLong(0)); + assertThatUnsignedLong(self.sut.delegates.count, equalToUnsignedLong(0)); [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; } @@ -272,10 +318,9 @@ - (void)testWithOneCustomDelegate { XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; SEL originalOpenURLiOS90Sel = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLiOS90Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -283,11 +328,10 @@ - (void)testWithOneCustomDelegate { [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; SEL customOpenURLiOS90Sel = @selector(application:openURL:options:returnedValue:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customOpenURLiOS90Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -296,9 +340,9 @@ - (void)testWithOneCustomDelegate { [customCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // When BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; @@ -321,10 +365,9 @@ - (void)testWithMultipleCustomOpenURLDelegates { XCTestExpectation *customCalledExpectation2 = [self expectationWithDescription:@"Custom delegate 2 called."]; MSApplication *appMock = self.appMock; SEL originalOpenURLiOS90Sel = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLiOS90Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -332,11 +375,10 @@ - (void)testWithMultipleCustomOpenURLDelegates { [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLiOS90Sel implementation:originalOpenURLiOS90Imp toInstance:originalAppDelegate]; SEL customOpenURLiOS90Sel = @selector(application:openURL:options:returnedValue:); id customAppDelegate1 = [self createCustomAppDelegateInstance]; id customOpenURLiOS90Imp1 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -345,10 +387,9 @@ - (void)testWithMultipleCustomOpenURLDelegates { [customCalledExpectation1 fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp1 toInstance:customAppDelegate1]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp1 toInstance:customAppDelegate1]; id customAppDelegate2 = [self createCustomAppDelegateInstance]; id customOpenURLiOS90Imp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -357,10 +398,10 @@ - (void)testWithMultipleCustomOpenURLDelegates { [customCalledExpectation2 fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp2 toInstance:customAppDelegate2]; - [MSAppDelegateForwarder addDelegate:customAppDelegate1]; - [MSAppDelegateForwarder addDelegate:customAppDelegate2]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp2 toInstance:customAppDelegate2]; + [self.sut addDelegate:customAppDelegate1]; + [self.sut addDelegate:customAppDelegate2]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // When BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; @@ -381,11 +422,10 @@ - (void)testWithRemovedCustomOpenURLDelegate { MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -394,24 +434,25 @@ - (void)testWithRemovedCustomOpenURLDelegate { [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSURL *url, __attribute__((unused)) NSString *sApplication, __attribute__((unused)) id annotation, __attribute__((unused)) BOOL returnedValue) { - // Then XCTFail(@"Custom delegate got called but is removed."); return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - [MSAppDelegateForwarder removeDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + [self.sut removeDelegate:customAppDelegate]; // When - BOOL returnedValue = - [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -429,11 +470,10 @@ - (void)testDontForwardOpenURLOnDisable { MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -442,29 +482,30 @@ - (void)testDontForwardOpenURLOnDisable { [originalCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSURL *url, __attribute__((unused)) NSString *sApplication, __attribute__((unused)) id annotation, __attribute__((unused)) BOOL returnedValue) { - // Then XCTFail(@"Custom delegate got called but is removed."); return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - MSAppDelegateForwarder.enabled = NO; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + self.sut.enabled = NO; // When - BOOL returnedValue = - [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; - MSAppDelegateForwarder.enabled = YES; + self.sut.enabled = YES; } #endif @@ -481,11 +522,10 @@ - (void)testReturnValueChaining { XCTestExpectation *customCalledExpectation2 = [self expectationWithDescription:@"Custom delegate 2 called."]; MSApplication *appMock = self.appMock; SEL originalOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS42Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalOpenURLiOS42Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, NSString *sApplication, id annotation) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -495,12 +535,11 @@ - (void)testReturnValueChaining { expectedReturnedValue = initialReturnValue; return expectedReturnedValue; }; - [self addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalOpenURLiOS42Sel implementation:originalOpenURLiOS42Imp toInstance:originalAppDelegate]; SEL customOpenURLiOS42Sel = @selector(application:openURL:sourceApplication:annotation:returnedValue:); id customAppDelegate1 = [self createCustomAppDelegateInstance]; id customOpenURLiOS42Imp1 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -511,11 +550,10 @@ - (void)testReturnValueChaining { [customCalledExpectation1 fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp1 toInstance:customAppDelegate1]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp1 toInstance:customAppDelegate1]; id customAppDelegate2 = [self createCustomAppDelegateInstance]; id customOpenURLiOS42Imp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, NSString *sApplication, id annotation, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -526,14 +564,16 @@ - (void)testReturnValueChaining { [customCalledExpectation2 fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp2 toInstance:customAppDelegate2]; - [MSAppDelegateForwarder addDelegate:customAppDelegate1]; - [MSAppDelegateForwarder addDelegate:customAppDelegate2]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS42Sel implementation:customOpenURLiOS42Imp2 toInstance:customAppDelegate2]; + [self.sut addDelegate:customAppDelegate1]; + [self.sut addDelegate:customAppDelegate2]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // When - BOOL returnedValue = - [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnnotation]; + BOOL returnedValue = [originalAppDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnnotation]; // Then assertThatBool(returnedValue, is(@(expectedReturnedValue))); @@ -549,12 +589,11 @@ - (void)testOpenURLMethodNotImplementedByOriginalDelegate { MSApplication *appMock = self.appMock; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; SEL originalOpenURLiOS90Sel = @selector(application:openURL:options:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; + [self.sut addDelegateSelectorToSwizzle:originalOpenURLiOS90Sel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL customOpenURLiOS90Sel = @selector(application:openURL:options:returnedValue:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customOpenURLiOS90Imp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSURL *url, id options, BOOL returnedValue) { - // Then assertThat(application, is(appMock)); assertThat(url, is(expectedURL)); @@ -563,9 +602,9 @@ - (void)testOpenURLMethodNotImplementedByOriginalDelegate { [customCalledExpectation fulfill]; return expectedReturnedValue; }; - [self addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customOpenURLiOS90Sel implementation:customOpenURLiOS90Imp toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // When BOOL returnedValue = [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; @@ -580,7 +619,7 @@ - (void)testDontSwizzleDeprecatedAPIIfNoAPIImplemented { // If // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; id expectedOptions = @{}; OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:NO]); @@ -589,11 +628,11 @@ - (void)testDontSwizzleDeprecatedAPIIfNoAPIImplemented { SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); SEL newSelector = @selector(application:openURL:options:); id originalAppDelegate = [self createOriginalAppDelegateInstance]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + [self.sut addDelegateSelectorToSwizzle:deprecatedSelector]; + [self.sut addDelegateSelectorToSwizzle:newSelector]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -607,11 +646,14 @@ - (void)testSwizzleDeprecatedAPIIfNoNewAPIImplemented { // If // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; id expectedAnotation = @{}; - OCMExpect( - [customDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation returnedValue:YES]); + OCMExpect([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); // App delegate implementing just the deprecated API. SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); @@ -622,20 +664,23 @@ - (void)testSwizzleDeprecatedAPIIfNoNewAPIImplemented { nbCalls++; return YES; }; - [self addSelector:deprecatedSelector implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + [MSDelegateForwarderTestUtil addSelector:deprecatedSelector implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:deprecatedSelector]; + [self.sut addDelegateSelectorToSwizzle:newSelector]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation]; // Then assertThatBool([originalAppDelegate respondsToSelector:newSelector], isFalse()); assertThatBool([originalAppDelegate respondsToSelector:deprecatedSelector], isTrue()); assertThatShort(nbCalls, equalToShort(1)); - OCMVerify( - [customDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation returnedValue:YES]); + OCMVerify([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); } - (void)testSwizzleDeprecatedAPIIfJustNewAPIImplemented { @@ -643,7 +688,7 @@ - (void)testSwizzleDeprecatedAPIIfJustNewAPIImplemented { // If // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; id expectedOptions = @{}; OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); @@ -657,12 +702,12 @@ - (void)testSwizzleDeprecatedAPIIfJustNewAPIImplemented { nbCalls++; return YES; }; - [self addSelector:newSelector implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + [MSDelegateForwarderTestUtil addSelector:newSelector implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:deprecatedSelector]; + [self.sut addDelegateSelectorToSwizzle:newSelector]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; // Then @@ -677,13 +722,16 @@ - (void)testSwizzleDeprecatedAPIIfAllAPIsImplemented { // If // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSURL *expectedURL = [NSURL URLWithString:@"https://www.contoso.com/sending-positive-waves"]; id expectedAnotation = @{}; id expectedOptions = @{}; OCMExpect([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); - OCMExpect( - [customDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation returnedValue:YES]); + OCMExpect([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); // App delegate implementing all the APIs. SEL deprecatedSelector = @selector(application:openURL:sourceApplication:annotation:); @@ -699,13 +747,13 @@ - (void)testSwizzleDeprecatedAPIIfAllAPIsImplemented { newSelectorNbCalls++; return YES; }; - [self addSelector:deprecatedSelector implementation:deprecatedSelectorImp toInstance:originalAppDelegate]; - [self addSelector:newSelector implementation:newSelectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:deprecatedSelector]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:newSelector]; + [MSDelegateForwarderTestUtil addSelector:deprecatedSelector implementation:deprecatedSelectorImp toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:newSelector implementation:newSelectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:deprecatedSelector]; + [self.sut addDelegateSelectorToSwizzle:newSelector]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation]; [originalAppDelegate application:self.appMock openURL:expectedURL options:expectedOptions]; @@ -715,51 +763,23 @@ - (void)testSwizzleDeprecatedAPIIfAllAPIsImplemented { assertThatShort(newSelectorNbCalls, equalToShort(1)); assertThatShort(deprecatedSelectorNbCalls, equalToShort(1)); OCMVerify([customDelegate application:self.appMock openURL:expectedURL options:expectedOptions returnedValue:YES]); - OCMVerify( - [customDelegate application:self.appMock openURL:expectedURL sourceApplication:nil annotation:expectedAnotation returnedValue:YES]); + OCMVerify([customDelegate application:self.appMock + openURL:expectedURL + sourceApplication:nil + annotation:expectedAnotation + returnedValue:YES]); } #endif -#pragma mark - Private - -- (NSString *)generateClassName { - return [@"C" stringByAppendingString:MS_UUID_STRING]; -} - -- (id)createInstanceConformingToProtocol:(Protocol *)protocol { - return [self createInstanceWithBaseClass:[NSObject class] andConformItToProtocol:protocol]; -} - -- (id)createInstanceWithBaseClass:(Class) class andConformItToProtocol:(Protocol *)protocol { - - // Generate class name to prevent conflicts in runtime added classes. - const char *name = [[self generateClassName] UTF8String]; - Class newClass = objc_allocateClassPair(class, name, 0); - if (protocol) { - class_addProtocol(newClass, protocol); - } - objc_registerClassPair(newClass); - return [newClass new]; -} +#pragma mark - helper - (id)createOriginalAppDelegateInstance { - return [self createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; + return [MSDelegateForwarderTestUtil createInstanceConformingToProtocol:@protocol(MSApplicationDelegate)]; } - (id)createCustomAppDelegateInstance { - return [self createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; -} - -- (void)addSelector:(SEL)selector implementation:(id)block toInstance:(id)instance { - [self addSelector:selector implementation:block toClass:[instance class]]; -} - -- (void)addSelector:(SEL)selector implementation:(id)block toClass:(id) class { - Method method = class_getInstanceMethod(class, selector); - const char *types = method_getTypeEncoding(method); - IMP imp = imp_implementationWithBlock(block); - class_addMethod(class, selector, imp, types); + return [MSDelegateForwarderTestUtil createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; } @end diff --git a/AppCenter/AppCenterTests/MSCSExtensionsTests.m b/AppCenter/AppCenterTests/MSCSExtensionsTests.m index eee78578bb..607ef6f29f 100644 --- a/AppCenter/AppCenterTests/MSCSExtensionsTests.m +++ b/AppCenter/AppCenterTests/MSCSExtensionsTests.m @@ -4,6 +4,7 @@ #import "MSCSModelConstants.h" #import "MSDeviceExtension.h" #import "MSLocExtension.h" +#import "MSMetadataExtension.h" #import "MSModelTestsUtililty.h" #import "MSNetExtension.h" #import "MSOSExtension.h" @@ -32,6 +33,8 @@ @interface MSCSExtensionsTests : XCTestCase @property(nonatomic) NSMutableDictionary *sdkExtDummyValues; @property(nonatomic) MSDeviceExtension *deviceExt; @property(nonatomic) NSMutableDictionary *deviceExtDummyValues; +@property(nonatomic) MSMetadataExtension *metadataExt; +@property(nonatomic) NSDictionary *metadataExtDummyValues; @property(nonatomic) MSCSData *data; @property(nonatomic) NSDictionary *dataDummyValues; @end @@ -59,6 +62,8 @@ - (void)setUp { self.sdkExt = [MSModelTestsUtililty sdkExtensionWithDummyValues:self.sdkExtDummyValues]; self.deviceExtDummyValues = [MSModelTestsUtililty deviceExtensionDummies]; self.deviceExt = [MSModelTestsUtililty deviceExtensionWithDummyValues:self.deviceExtDummyValues]; + self.metadataExtDummyValues = [MSModelTestsUtililty metadataExtensionDummies]; + self.metadataExt = [MSModelTestsUtililty metadataExtensionWithDummyValues:self.metadataExtDummyValues]; self.dataDummyValues = [MSModelTestsUtililty dataDummies]; self.data = [MSModelTestsUtililty dataWithDummyValues:self.dataDummyValues]; self.extDummyValues = [MSModelTestsUtililty extensionDummies]; @@ -86,6 +91,7 @@ - (void)testExtJSONSerializingToDictionary { XCTAssertEqualObjects(dict[kMSCSProtocolExt], [self.extDummyValues[kMSCSProtocolExt] serializeToDictionary]); XCTAssertEqualObjects(dict[kMSCSOSExt], [self.extDummyValues[kMSCSOSExt] serializeToDictionary]); XCTAssertEqualObjects(dict[kMSCSDeviceExt], [self.extDummyValues[kMSCSDeviceExt] serializeToDictionary]); + XCTAssertEqualObjects(dict[kMSCSMetadataExt], [self.extDummyValues[kMSCSMetadataExt] serializeToDictionary]); } - (void)testExtNSCodingSerializationAndDeserialization { @@ -98,6 +104,7 @@ - (void)testExtNSCodingSerializationAndDeserialization { XCTAssertNotNil(actualExt); XCTAssertEqualObjects(self.ext, actualExt); XCTAssertTrue([actualExt isMemberOfClass:[MSCSExtensions class]]); + XCTAssertEqualObjects(actualExt.metadataExt, self.extDummyValues[kMSCSMetadataExt]); XCTAssertEqualObjects(actualExt.userExt, self.extDummyValues[kMSCSUserExt]); XCTAssertEqualObjects(actualExt.locExt, self.extDummyValues[kMSCSLocExt]); XCTAssertEqualObjects(actualExt.appExt, self.extDummyValues[kMSCSAppExt]); @@ -131,6 +138,13 @@ - (void)testExtIsEqual { XCTAssertEqualObjects(anotherExt, self.ext); // If + anotherExt.metadataExt = OCMClassMock([MSMetadataExtension class]); + + // Then + XCTAssertNotEqualObjects(anotherExt, self.ext); + + // If + anotherExt.metadataExt = self.extDummyValues[kMSCSMetadataExt]; anotherExt.userExt = OCMClassMock([MSUserExtension class]); // Then @@ -179,6 +193,61 @@ - (void)testExtIsEqual { XCTAssertNotEqualObjects(anotherExt, self.ext); } +#pragma mark - MSMetadataExtension + +- (void)testMetadataExtJSONSerializingToDictionary { + + // When + NSMutableDictionary *dict = [self.metadataExt serializeToDictionary]; + + // Then + XCTAssertNotNil(dict); + XCTAssertEqualObjects(dict, self.metadataExtDummyValues); +} + +- (void)testMetadataExtNSCodingSerializationAndDeserialization { + + // When + NSData *serializedMetadataExt = [NSKeyedArchiver archivedDataWithRootObject:self.metadataExt]; + MSMetadataExtension *actualMetadataExt = [NSKeyedUnarchiver unarchiveObjectWithData:serializedMetadataExt]; + + // Then + XCTAssertNotNil(actualMetadataExt); + XCTAssertEqualObjects(self.metadataExt, actualMetadataExt); + XCTAssertTrue([actualMetadataExt isMemberOfClass:[MSMetadataExtension class]]); + XCTAssertEqualObjects(actualMetadataExt.metadata, self.metadataExtDummyValues); +} + +- (void)testMetadataExtIsValid { + + // If + MSMetadataExtension *metadataExt = [MSMetadataExtension new]; + + // Then + XCTAssertTrue([metadataExt isValid]); +} + +- (void)testMetadataExtIsEqual { + + // If + MSMetadataExtension *anotherMetadataExt = [MSMetadataExtension new]; + + // Then + XCTAssertNotEqualObjects(anotherMetadataExt, self.metadataExt); + + // If + anotherMetadataExt = [MSModelTestsUtililty metadataExtensionWithDummyValues:self.metadataExtDummyValues]; + + // Then + XCTAssertEqualObjects(anotherMetadataExt, self.metadataExt); + + // If + anotherMetadataExt.metadata = @{}; + + // Then + XCTAssertNotEqualObjects(anotherMetadataExt, self.metadataExt); +} + #pragma mark - MSUserExtension - (void)testUserExtJSONSerializingToDictionary { @@ -550,7 +619,7 @@ - (void)testSDKExtJSONSerializingToDictionary { NSMutableDictionary *dict = [self.sdkExt serializeToDictionary]; // Then - self.sdkExtDummyValues[kMSSDKInstallId] = [((NSUUID *)self.sdkExtDummyValues[kMSSDKInstallId])UUIDString]; + self.sdkExtDummyValues[kMSSDKInstallId] = [((NSUUID *)self.sdkExtDummyValues[kMSSDKInstallId]) UUIDString]; XCTAssertNotNil(dict); XCTAssertEqualObjects(dict, self.sdkExtDummyValues); } @@ -726,7 +795,7 @@ - (void)testDataIsEqual { XCTAssertEqualObjects(anotherData, self.data); // If - anotherData.properties = [@{ @"part.c.key" : @"part.c.value" } mutableCopy]; + anotherData.properties = [@{@"part.c.key" : @"part.c.value"} mutableCopy]; // Then XCTAssertNotEqualObjects(anotherData, self.data); diff --git a/AppCenter/AppCenterTests/MSChannelGroupDefaultTests.m b/AppCenter/AppCenterTests/MSChannelGroupDefaultTests.m index 7781945d51..fb6abad600 100644 --- a/AppCenter/AppCenterTests/MSChannelGroupDefaultTests.m +++ b/AppCenter/AppCenterTests/MSChannelGroupDefaultTests.m @@ -91,8 +91,8 @@ - (void)testAddChannelWithCustomIngestion { MSChannelGroupDefault *sut = [[MSChannelGroupDefault alloc] initWithIngestion:ingestionMockDefault]; // When - MSChannelUnitDefault *channelUnit = - (MSChannelUnitDefault *)[sut addChannelUnitWithConfiguration:[MSChannelUnitConfiguration new] withIngestion:ingestionMockCustom]; + MSChannelUnitDefault *channelUnit = (MSChannelUnitDefault *)[sut addChannelUnitWithConfiguration:[MSChannelUnitConfiguration new] + withIngestion:ingestionMockCustom]; // Then XCTAssertNotEqual(ingestionMockDefault, channelUnit.ingestion); @@ -113,7 +113,7 @@ - (void)testDelegatesConcurrentAccess { // When void (^block)(void) = ^{ for (int i = 0; i < 10; i++) { - [addedChannel enqueueItem:log]; + [addedChannel enqueueItem:log flags:MSFlagsDefault]; } for (int i = 0; i < 100; i++) { [sut addDelegate:OCMProtocolMock(@protocol(MSChannelDelegate))]; @@ -322,10 +322,10 @@ - (void)testDelegateCalledWhenChannelUnitDidPrepareLog { [sut addDelegate:delegateMock]; // When - [sut channel:channelUnitMock didPrepareLog:mockLog withInternalId:internalId]; + [sut channel:channelUnitMock didPrepareLog:mockLog internalId:internalId flags:MSFlagsDefault]; // Then - OCMVerify([delegateMock channel:channelUnitMock didPrepareLog:mockLog withInternalId:internalId]); + OCMVerify([delegateMock channel:channelUnitMock didPrepareLog:mockLog internalId:internalId flags:MSFlagsDefault]); // Clear [channelUnitMock stopMocking]; @@ -347,10 +347,10 @@ - (void)testDelegateCalledWhenChannelUnitDidCompleteEnqueueingLog { [sut addDelegate:delegateMock]; // When - [sut channel:channelUnitMock didCompleteEnqueueingLog:mockLog withInternalId:internalId]; + [sut channel:channelUnitMock didCompleteEnqueueingLog:mockLog internalId:internalId]; // Then - OCMVerify([delegateMock channel:channelUnitMock didCompleteEnqueueingLog:mockLog withInternalId:internalId]); + OCMVerify([delegateMock channel:channelUnitMock didCompleteEnqueueingLog:mockLog internalId:internalId]); // Clear [channelUnitMock stopMocking]; diff --git a/AppCenter/AppCenterTests/MSChannelUnitDefaultTests.m b/AppCenter/AppCenterTests/MSChannelUnitDefaultTests.m index bf2751f9c1..40e3a31cb9 100644 --- a/AppCenter/AppCenterTests/MSChannelUnitDefaultTests.m +++ b/AppCenter/AppCenterTests/MSChannelUnitDefaultTests.m @@ -1,4 +1,5 @@ #import +#import #import "MSAbstractLogInternal.h" #import "MSChannelDelegate.h" @@ -39,7 +40,7 @@ - (void)enqueueChannelEndJobExpectation; @implementation MSChannelUnitDefaultTests -#pragma mark - Houskeeping +#pragma mark - Housekeeping - (void)setUp { [super setUp]; @@ -47,7 +48,8 @@ - (void)setUp { self.logsDispatchQueue = dispatch_get_main_queue(); self.configMock = OCMClassMock([MSChannelUnitConfiguration class]); self.storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY]).andReturn(YES); + OCMStub([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsPersistenceNormal]).andReturn(YES); + OCMStub([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsPersistenceCritical]).andReturn(YES); self.ingestionMock = OCMProtocolMock(@protocol(MSIngestionProtocol)); OCMStub([self.ingestionMock isReadyToSend]).andReturn(YES); self.sut = [[MSChannelUnitDefault alloc] initWithIngestion:self.ingestionMock @@ -89,7 +91,6 @@ - (void)testLogsSentWithSuccess { id ingestionMock = OCMProtocolMock(@protocol(MSIngestionProtocol)); OCMStub([ingestionMock isReadyToSend]).andReturn(YES); OCMStub([ingestionMock sendAsync:OCMOCK_ANY completionHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { - // Get ingestion block for later call. [invocation retainArguments]; [invocation getArgument:&ingestionBlock atIndex:3]; @@ -98,7 +99,7 @@ - (void)testLogsSentWithSuccess { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMStub([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY]).andReturn(YES); + OCMStub([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(YES); OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit excludedTargetKeys:OCMOCK_ANY completionHandler:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionHandler loadCallback; @@ -125,15 +126,14 @@ - (void)testLogsSentWithSuccess { OCMReject([delegateMock channel:sut didFailSendingLog:OCMOCK_ANY withError:OCMOCK_ANY]); OCMExpect([delegateMock channel:sut didSucceedSendingLog:expectedLog]); OCMExpect([delegateMock channel:sut prepareLog:enqueuedLog]); - OCMExpect([delegateMock channel:sut didPrepareLog:enqueuedLog withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:enqueuedLog withInternalId:OCMOCK_ANY]); + OCMExpect([delegateMock channel:sut didPrepareLog:enqueuedLog internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:enqueuedLog internalId:OCMOCK_ANY]); OCMExpect([storageMock deleteLogsWithBatchId:expectedBatchId groupId:kMSTestGroupId]); // When dispatch_async(self.logsDispatchQueue, ^{ - // Enqueue now that the delegate is set. - [sut enqueueItem:enqueuedLog]; + [sut enqueueItem:enqueuedLog flags:MSFlagsDefault]; // Try to release one batch. dispatch_async(self.logsDispatchQueue, ^{ @@ -152,7 +152,6 @@ - (void)testLogsSentWithSuccess { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Get sure it has been sent. assertThat(logContainer.batchId, is(expectedBatchId)); assertThat(logContainer.logs, is(@[ expectedLog ])); @@ -183,7 +182,6 @@ - (void)testLogsSentWithFailure { id ingestionMock = OCMProtocolMock(@protocol(MSIngestionProtocol)); OCMStub([ingestionMock isReadyToSend]).andReturn(YES); OCMStub([ingestionMock sendAsync:OCMOCK_ANY completionHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { - // Get ingestion block for later call. [invocation retainArguments]; [invocation getArgument:&ingestionBlock atIndex:3]; @@ -192,6 +190,7 @@ - (void)testLogsSentWithFailure { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); + OCMStub([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(YES); OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit excludedTargetKeys:OCMOCK_ANY completionHandler:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionHandler loadCallback; @@ -217,15 +216,14 @@ - (void)testLogsSentWithFailure { [sut addDelegate:delegateMock]; OCMExpect([delegateMock channel:sut didFailSendingLog:expectedLog withError:OCMOCK_ANY]); OCMReject([delegateMock channel:sut didSucceedSendingLog:OCMOCK_ANY]); - OCMExpect([delegateMock channel:sut didPrepareLog:enqueuedLog withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:enqueuedLog withInternalId:OCMOCK_ANY]); + OCMExpect([delegateMock channel:sut didPrepareLog:enqueuedLog internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:enqueuedLog internalId:OCMOCK_ANY]); OCMExpect([storageMock deleteLogsWithBatchId:expectedBatchId groupId:kMSTestGroupId]); // When dispatch_async(self.logsDispatchQueue, ^{ - // Enqueue now that the delegate is set. - [sut enqueueItem:enqueuedLog]; + [sut enqueueItem:enqueuedLog flags:MSFlagsDefault]; // Try to release one batch. dispatch_async(self.logsDispatchQueue, ^{ @@ -242,7 +240,6 @@ - (void)testLogsSentWithFailure { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Get sure it has been sent. assertThat(logContainer.batchId, is(expectedBatchId)); assertThat(logContainer.logs, is(@[ expectedLog ])); @@ -270,7 +267,7 @@ - (void)testEnqueuingItemsWillIncreaseCounter { // When for (int i = 1; i <= itemsToAdd; i++) { - [self.sut enqueueItem:[self getValidMockLog]]; + [self.sut enqueueItem:[self getValidMockLog] flags:MSFlagsDefault]; } [self enqueueChannelEndJobExpectation]; @@ -284,6 +281,101 @@ - (void)testEnqueuingItemsWillIncreaseCounter { }]; } +- (void)testNotCheckingPendingLogsOnEnqueueFailure { + + // If + [self initChannelEndJobExpectation]; + self.configMock = [[MSChannelUnitConfiguration alloc] initWithGroupId:kMSTestGroupId + priority:MSPriorityDefault + flushInterval:5 + batchSizeLimit:10 + pendingBatchesLimit:3]; + self.storageMock = OCMProtocolMock(@protocol(MSStorage)); + OCMStub([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(NO); + self.sut = [[MSChannelUnitDefault alloc] initWithIngestion:self.ingestionMock + storage:self.storageMock + configuration:self.configMock + logsDispatchQueue:self.logsDispatchQueue]; + id channelUnitMock = OCMPartialMock(self.sut); + OCMReject([channelUnitMock checkPendingLogs]); + int itemsToAdd = 3; + + // When + for (int i = 1; i <= itemsToAdd; i++) { + [self.sut enqueueItem:[self getValidMockLog] flags:MSFlagsDefault]; + } + [self enqueueChannelEndJobExpectation]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(NSError *error) { + assertThatUnsignedLong(self.sut.itemsCount, equalToInt(0)); + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (void)testEnqueueCriticalItem { + + // If + [self initChannelEndJobExpectation]; + id mockLog = [self getValidMockLog]; + + // When + [self.sut enqueueItem:mockLog flags:MSFlagsPersistenceCritical]; + [self enqueueChannelEndJobExpectation]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(NSError *error) { + OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY flags:MSFlagsPersistenceCritical]); + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (void)testEnqueueNonCriticalItem { + + // If + [self initChannelEndJobExpectation]; + id mockLog = [self getValidMockLog]; + + // When + [self.sut enqueueItem:mockLog flags:MSFlagsPersistenceNormal]; + [self enqueueChannelEndJobExpectation]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(NSError *error) { + OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY flags:MSFlagsPersistenceNormal]); + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (void)testEnqueueItemWithFlagsDefault { + + // If + [self initChannelEndJobExpectation]; + id mockLog = [self getValidMockLog]; + + // When + [self.sut enqueueItem:mockLog flags:MSFlagsDefault]; + [self enqueueChannelEndJobExpectation]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(NSError *error) { + OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY flags:MSFlagsDefault]); + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + - (void)testQueueFlushedAfterBatchSizeReached { // If @@ -302,19 +394,18 @@ - (void)testQueueFlushedAfterBatchSizeReached { XCTestExpectation *expectation = [self expectationWithDescription:@"All items enqueued"]; id mockLog = [self getValidMockLog]; id delegateMock = OCMProtocolMock(@protocol(MSChannelDelegate)); - OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog withInternalId:OCMOCK_ANY]) - .andDo(^(__unused NSInvocation *invocation) { - static int count = 0; - count++; - if (count == itemsToAdd) { - [expectation fulfill]; - } - }); + OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog internalId:OCMOCK_ANY]).andDo(^(__unused NSInvocation *invocation) { + static int count = 0; + count++; + if (count == itemsToAdd) { + [expectation fulfill]; + } + }); [sut addDelegate:delegateMock]; // When for (int i = 0; i < itemsToAdd; ++i) { - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsPersistenceCritical]; } [self enqueueChannelEndJobExpectation]; @@ -341,7 +432,6 @@ - (void)testBatchQueueLimit { id ingestionMock = OCMProtocolMock(@protocol(MSIngestionProtocol)); OCMStub([ingestionMock isReadyToSend]).andReturn(YES); OCMStub([ingestionMock sendAsync:OCMOCK_ANY completionHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { - MSLogContainer *container; [invocation getArgument:&container atIndex:2]; if (container) { @@ -349,6 +439,7 @@ - (void)testBatchQueueLimit { } }); id storageMock = OCMProtocolMock(@protocol(MSStorage)); + OCMStub([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(YES); OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit excludedTargetKeys:OCMOCK_ANY completionHandler:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionHandler loadCallback; @@ -370,7 +461,7 @@ - (void)testBatchQueueLimit { // When for (NSUInteger i = 1; i <= expectedMaxPendingBatched + 1; i++) { - [sut enqueueItem:[self getValidMockLog]]; + [sut enqueueItem:[self getValidMockLog] flags:MSFlagsDefault]; } [self enqueueChannelEndJobExpectation]; @@ -402,7 +493,6 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { id ingestionMock = OCMProtocolMock(@protocol(MSIngestionProtocol)); OCMStub([ingestionMock isReadyToSend]).andReturn(YES); OCMStub([ingestionMock sendAsync:OCMOCK_ANY completionHandler:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { - // Get ingestion block for later call. [invocation retainArguments]; [invocation getArgument:&ingestionBlock atIndex:3]; @@ -411,6 +501,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // Stub the storage load for that log. id storageMock = OCMProtocolMock(@protocol(MSStorage)); + OCMStub([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(YES); OCMStub([storageMock loadLogsWithGroupId:kMSTestGroupId limit:batchSizeLimit excludedTargetKeys:OCMOCK_ANY completionHandler:OCMOCK_ANY]) .andDo(^(NSInvocation *invocation) { MSLoadDataCompletionHandler loadCallback; @@ -435,7 +526,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { logsDispatchQueue:dispatch_get_main_queue()]; // When - [sut enqueueItem:[self getValidMockLog]]; + [sut enqueueItem:[self getValidMockLog] flags:MSFlagsDefault]; // Try to release one batch. dispatch_async(self.logsDispatchQueue, ^{ @@ -446,7 +537,6 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // Then dispatch_async(self.logsDispatchQueue, ^{ - // Batch queue should not be full; assertThatBool(sut.pendingBatchQueueFull, isFalse()); [oneLogSentExpectation fulfill]; @@ -454,7 +544,7 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // When // Send another batch. currentBatchId++; - [sut enqueueItem:[self getValidMockLog]]; + [sut enqueueItem:[self getValidMockLog] flags:MSFlagsDefault]; [self enqueueChannelEndJobExpectation]; }); }); @@ -462,7 +552,6 @@ - (void)testNextBatchSentIfPendingQueueGotRoomAgain { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Get sure it has been sent. assertThat(lastBatchLogContainer.batchId, is([@(currentBatchId) stringValue])); if (error) { @@ -497,7 +586,7 @@ - (void)testDontForwardLogsToIngestionOnDisabled { logsDispatchQueue:dispatch_get_main_queue()]; // When [sut setEnabled:NO andDeleteDataOnDisabled:NO]; - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; [self enqueueChannelEndJobExpectation]; // Then @@ -534,14 +623,13 @@ - (void)testDeleteDataOnDisabled { self.sut.configuration = config; // When - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; [sut setEnabled:NO andDeleteDataOnDisabled:YES]; [self enqueueChannelEndJobExpectation]; // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Check that logs as been requested for // deletion and that there is no batch left. OCMVerify([storageMock deleteLogsWithGroupId:kMSTestGroupId]); @@ -556,18 +644,17 @@ - (void)testDontSaveLogsWhileDisabledWithDataDeletion { // If [self initChannelEndJobExpectation]; id mockLog = [self getValidMockLog]; - OCMReject([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY]); + OCMReject([self.storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]); MSChannelUnitDefault *sut = [self createChannelUnit]; id delegateMock = OCMProtocolMock(@protocol(MSChannelDelegate)); - OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog withInternalId:OCMOCK_ANY]) - .andDo(^(__unused NSInvocation *invocation) { - [self enqueueChannelEndJobExpectation]; - }); + OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog internalId:OCMOCK_ANY]).andDo(^(__unused NSInvocation *invocation) { + [self enqueueChannelEndJobExpectation]; + }); [sut addDelegate:delegateMock]; // When [sut setEnabled:NO andDeleteDataOnDisabled:YES]; - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; // Then [self waitForExpectationsWithTimeout:1 @@ -587,21 +674,20 @@ - (void)testSaveLogsAfterReEnabled { [sut setEnabled:NO andDeleteDataOnDisabled:YES]; id mockLog = [self getValidMockLog]; id delegateMock = OCMProtocolMock(@protocol(MSChannelDelegate)); - OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog withInternalId:OCMOCK_ANY]) - .andDo(^(__unused NSInvocation *invocation) { - [self enqueueChannelEndJobExpectation]; - }); + OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:mockLog internalId:OCMOCK_ANY]).andDo(^(__unused NSInvocation *invocation) { + [self enqueueChannelEndJobExpectation]; + }); [sut addDelegate:delegateMock]; // When [sut setEnabled:YES andDeleteDataOnDisabled:NO]; - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { assertThatBool(sut.discardLogs, isFalse()); - OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY]); + OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY flags:MSFlagsDefault]); if (error) { XCTFail(@"Expectation Failed with error: %@", error); } @@ -611,20 +697,20 @@ - (void)testSaveLogsAfterReEnabled { [self initChannelEndJobExpectation]; id otherMockLog = [self getValidMockLog]; [sut setEnabled:NO andDeleteDataOnDisabled:NO]; - OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:otherMockLog withInternalId:OCMOCK_ANY]) + OCMStub([delegateMock channel:sut didCompleteEnqueueingLog:otherMockLog internalId:OCMOCK_ANY]) .andDo(^(__unused NSInvocation *invocation) { [self enqueueChannelEndJobExpectation]; }); // When [sut setEnabled:YES andDeleteDataOnDisabled:NO]; - [sut enqueueItem:otherMockLog]; + [sut enqueueItem:otherMockLog flags:MSFlagsDefault]; // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { assertThatBool(sut.discardLogs, isFalse()); - OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY]); + OCMVerify([self.storageMock saveLog:mockLog withGroupId:OCMOCK_ANY flags:MSFlagsDefault]); if (error) { XCTFail(@"Expectation Failed with error: %@", error); } @@ -709,17 +795,16 @@ - (void)testDelegateAfterChannelDisabled { // Enqueue now that the delegate is set. dispatch_async(self.logsDispatchQueue, ^{ - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; [self enqueueChannelEndJobExpectation]; }); // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Check the callbacks were invoked for logs. - OCMVerify([delegateMock channel:sut didPrepareLog:mockLog withInternalId:OCMOCK_ANY]); - OCMVerify([delegateMock channel:sut didCompleteEnqueueingLog:mockLog withInternalId:OCMOCK_ANY]); + OCMVerify([delegateMock channel:sut didPrepareLog:mockLog internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMVerify([delegateMock channel:sut didCompleteEnqueueingLog:mockLog internalId:OCMOCK_ANY]); OCMVerify([delegateMock channel:sut willSendLog:mockLog]); OCMVerify([delegateMock channel:sut didFailSendingLog:mockLog withError:anything()]); if (error) { @@ -751,7 +836,6 @@ - (void)testDelegateAfterChannelPaused { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Check the callbacks were invoked for logs. OCMVerify([delegateMock channel:sut didPauseWithIdentifyingObject:identifyingObject]); if (error) { @@ -783,7 +867,6 @@ - (void)testDelegateAfterChannelResumed { // Then [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - // Check the callbacks were invoked for logs. OCMVerify([delegateMock channel:sut didResumeWithIdentifyingObject:identifyingObject]); if (error) { @@ -801,7 +884,7 @@ - (void)testDeviceAndTimestampAreAddedOnEnqueuing { MSChannelUnitDefault *sut = [self createChannelUnit]; // When - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; // Then XCTAssertNotNil(mockLog.device); @@ -817,7 +900,7 @@ - (void)testDeviceAndTimestampAreNotOverwrittenOnEnqueuing { MSChannelUnitDefault *sut = [self createChannelUnit]; // When - [sut enqueueItem:mockLog]; + [sut enqueueItem:mockLog flags:MSFlagsDefault]; // Then XCTAssertEqual(mockLog.device, device); @@ -829,7 +912,7 @@ - (void)testEnqueuingLogDoesNotPersistFilteredLogs { // If [self initChannelEndJobExpectation]; id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMReject([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY]); + OCMReject([storageMock saveLog:OCMOCK_ANY withGroupId:OCMOCK_ANY flags:MSFlagsDefault]); MSChannelUnitDefault *sut = [[MSChannelUnitDefault alloc] initWithIngestion:self.ingestionMock storage:storageMock configuration:self.configMock @@ -841,18 +924,17 @@ - (void)testEnqueuingLogDoesNotPersistFilteredLogs { OCMStub([delegateMock2 channelUnit:sut shouldFilterLog:log]).andReturn(NO); OCMExpect([delegateMock channel:sut prepareLog:log]); OCMExpect([delegateMock2 channel:sut prepareLog:log]); - OCMExpect([delegateMock channel:sut didPrepareLog:log withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock2 channel:sut didPrepareLog:log withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:log withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock2 channel:sut didCompleteEnqueueingLog:log withInternalId:OCMOCK_ANY]); + OCMExpect([delegateMock channel:sut didPrepareLog:log internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMExpect([delegateMock2 channel:sut didPrepareLog:log internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:log internalId:OCMOCK_ANY]); + OCMExpect([delegateMock2 channel:sut didCompleteEnqueueingLog:log internalId:OCMOCK_ANY]); [sut addDelegate:delegateMock]; [sut addDelegate:delegateMock2]; // When dispatch_async(self.logsDispatchQueue, ^{ - // Enqueue now that the delegate is set. - [sut enqueueItem:log]; + [sut enqueueItem:log flags:MSFlagsDefault]; [self enqueueChannelEndJobExpectation]; }); @@ -874,23 +956,22 @@ - (void)testEnqueuingLogPersistsUnfilteredLogs { [self initChannelEndJobExpectation]; id log = [self getValidMockLog]; id storageMock = OCMProtocolMock(@protocol(MSStorage)); - OCMExpect([storageMock saveLog:log withGroupId:self.configMock.groupId]); + OCMExpect([storageMock saveLog:log withGroupId:self.configMock.groupId flags:MSFlagsDefault]); MSChannelUnitDefault *sut = [[MSChannelUnitDefault alloc] initWithIngestion:self.ingestionMock storage:storageMock configuration:self.configMock logsDispatchQueue:self.logsDispatchQueue]; - OCMStub([sut.storage saveLog:log withGroupId:OCMOCK_ANY]).andReturn(YES); + OCMStub([sut.storage saveLog:log withGroupId:OCMOCK_ANY flags:MSFlagsDefault]).andReturn(YES); id delegateMock = OCMProtocolMock(@protocol(MSChannelDelegate)); OCMStub([delegateMock channelUnit:sut shouldFilterLog:log]).andReturn(NO); - OCMExpect([delegateMock channel:sut didPrepareLog:log withInternalId:OCMOCK_ANY]); - OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:log withInternalId:OCMOCK_ANY]); + OCMExpect([delegateMock channel:sut didPrepareLog:log internalId:OCMOCK_ANY flags:MSFlagsDefault]); + OCMExpect([delegateMock channel:sut didCompleteEnqueueingLog:log internalId:OCMOCK_ANY]); [sut addDelegate:delegateMock]; // When dispatch_async(self.logsDispatchQueue, ^{ - // Enqueue now that the delegate is set. - [sut enqueueItem:log]; + [sut enqueueItem:log flags:MSFlagsDefault]; [self enqueueChannelEndJobExpectation]; }); @@ -1140,7 +1221,7 @@ - (void)testLogsStoredWhenTargetKeyIsPaused { log.iKey = targetKey; // When - [self.sut enqueueItem:log]; + [self.sut enqueueItem:log flags:MSFlagsDefault]; // Then [self enqueueChannelEndJobExpectation]; @@ -1149,7 +1230,7 @@ - (void)testLogsStoredWhenTargetKeyIsPaused { if (error) { XCTFail(@"Expectation Failed with error: %@", error); } - OCMVerify([self.storageMock saveLog:log withGroupId:self.sut.configuration.groupId]); + OCMVerify([self.storageMock saveLog:log withGroupId:self.sut.configuration.groupId flags:MSFlagsDefault]); }]; } diff --git a/AppCenter/AppCenterTests/MSCommonSchemaLogTests.m b/AppCenter/AppCenterTests/MSCommonSchemaLogTests.m index bb4b08431e..3ef7da77fa 100644 --- a/AppCenter/AppCenterTests/MSCommonSchemaLogTests.m +++ b/AppCenter/AppCenterTests/MSCommonSchemaLogTests.m @@ -4,6 +4,7 @@ #import "MSCSExtensions.h" #import "MSCSModelConstants.h" #import "MSCommonSchemaLog.h" +#import "MSConstants.h" #import "MSDevice.h" #import "MSLocExtension.h" #import "MSModelTestsUtililty.h" @@ -32,6 +33,7 @@ - (void)setUp { kMSCSName : @"1DS", kMSCSTime : abstractDummies[kMSTimestamp], kMSCSIKey : @"o:60cd0b94-6060-11e8-9c2d-fa7ae01bbebc", + kMSCSFlags : @(MSFlagsPersistenceNormal), kMSCSExt : [self extWithDummyValues], kMSCSData : [self dataWithDummyValues] } mutableCopy]; @@ -47,20 +49,23 @@ - (void)tearDown { - (void)testCSLogJSONSerializingToDictionary { + // If + NSDictionary *expectedSerializedLog = @{ + kMSCSTime : [MSUtility dateToISO8601:self.csLogDummyValues[kMSCSTime]], + kMSCSData : [self.csLogDummyValues[kMSCSData] serializeToDictionary], + kMSCSExt : [self.csLogDummyValues[kMSCSExt] serializeToDictionary], + kMSCSIKey : @"o:60cd0b94-6060-11e8-9c2d-fa7ae01bbebc", + kMSCSVer : @"3.0", + kMSCSName : @"1DS", + kMSCSFlags : @(MSFlagsPersistenceNormal) + }; + // When - NSMutableDictionary *dict = [self.commonSchemaLog serializeToDictionary]; + NSMutableDictionary *serializedLog = [self.commonSchemaLog serializeToDictionary]; // Then - self.csLogDummyValues[kMSCSTime] = [MSUtility dateToISO8601:self.csLogDummyValues[kMSCSTime]]; - self.csLogDummyValues[kMSCSData] = [self.csLogDummyValues[kMSCSData] serializeToDictionary]; - self.csLogDummyValues[kMSCSExt] = [self.csLogDummyValues[kMSCSExt] serializeToDictionary]; - [self.csLogDummyValues removeObjectForKey:kMSDevice]; - [self.csLogDummyValues removeObjectForKey:kMSDistributionGroupId]; - [self.csLogDummyValues removeObjectForKey:kMSTimestamp]; - [self.csLogDummyValues removeObjectForKey:kMSType]; - [self.csLogDummyValues removeObjectForKey:kMSSId]; - XCTAssertNotNil(dict); - XCTAssertEqualObjects(dict, self.csLogDummyValues); + XCTAssertNotNil(serializedLog); + XCTAssertEqualObjects(serializedLog, expectedSerializedLog); } - (void)testCSLogNSCodingSerializationAndDeserialization { @@ -195,6 +200,13 @@ - (void)testCSLogIsEqual { // If anotherCommonSchemaLog.data = self.csLogDummyValues[kMSCSData]; + anotherCommonSchemaLog.flags = -1; + + // Then + XCTAssertNotEqualObjects(anotherCommonSchemaLog, self.commonSchemaLog); + + // If + anotherCommonSchemaLog.flags = [self.csLogDummyValues[kMSCSFlags] longLongValue]; // Then XCTAssertEqualObjects(anotherCommonSchemaLog, self.commonSchemaLog); @@ -265,7 +277,7 @@ - (MSSDKExtension *)sdkExtWithDummyValues { - (MSCSData *)dataWithDummyValues { MSCSData *data = [MSCSData new]; - data.properties = @{ @"Jan" : @"1", @"feb" : @"2", @"Mar" : @"3" }; + data.properties = @{@"Jan" : @"1", @"feb" : @"2", @"Mar" : @"3"}; return data; } @@ -275,6 +287,7 @@ - (MSCommonSchemaLog *)csLogWithDummyValues:(NSDictionary *)dummyValues { csLog.name = dummyValues[kMSCSName]; csLog.timestamp = dummyValues[kMSCSTime]; csLog.iKey = dummyValues[kMSCSIKey]; + csLog.flags = [dummyValues[kMSCSFlags] longLongValue]; csLog.ext = dummyValues[kMSCSExt]; csLog.data = dummyValues[kMSCSData]; [MSModelTestsUtililty populateAbstractLogWithDummies:csLog]; diff --git a/AppCenter/AppCenterTests/MSCustomPropertiesTests.m b/AppCenter/AppCenterTests/MSCustomPropertiesTests.m index b1b2e7da46..c8ce0bd2c7 100644 --- a/AppCenter/AppCenterTests/MSCustomPropertiesTests.m +++ b/AppCenter/AppCenterTests/MSCustomPropertiesTests.m @@ -1,7 +1,7 @@ #import #import "MSCustomProperties.h" -#import "MSCustomPropertiesPrivate.h" +#import "MSCustomPropertiesInternal.h" #import "MSTestFrameworks.h" @interface MSCustomPropertiesTests : XCTestCase @@ -22,7 +22,7 @@ - (void)testKeyValidate { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Null key. // When @@ -34,7 +34,7 @@ - (void)testKeyValidate { [customProperties clearPropertyForKey:nullKey]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Invalid key. // When @@ -46,7 +46,7 @@ - (void)testKeyValidate { [customProperties clearPropertyForKey:invalidKey]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Too long key. // When @@ -58,7 +58,7 @@ - (void)testKeyValidate { [customProperties clearPropertyForKey:tooLongKey]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Normal keys. // When @@ -71,7 +71,7 @@ - (void)testKeyValidate { [customProperties setString:string forKey:maxLongKey]; // Then - assertThat([customProperties properties], hasCountOf(6)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(6)); // Already contains keys. // When @@ -83,7 +83,7 @@ - (void)testKeyValidate { [customProperties setString:string forKey:maxLongKey]; // Then - assertThat([customProperties properties], hasCountOf(6)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(6)); } - (void)testMaxPropertiesCount { @@ -99,20 +99,20 @@ - (void)testMaxPropertiesCount { } // Then - assertThat([customProperties properties], hasCountOf(maxPropertiesCount)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(maxPropertiesCount)); // Exceeding maximum properties count. // When [customProperties setNumber:@(1) forKey:@"extra1"]; // Then - assertThat([customProperties properties], hasCountOf(maxPropertiesCount)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(maxPropertiesCount)); // When [customProperties setNumber:@(1) forKey:@"extra2"]; // Then - assertThat([customProperties properties], hasCountOf(maxPropertiesCount)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(maxPropertiesCount)); } - (void)testSetString { @@ -124,7 +124,7 @@ - (void)testSetString { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Null value. // When @@ -132,7 +132,7 @@ - (void)testSetString { [customProperties setString:nullValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Too long value. // When @@ -140,7 +140,7 @@ - (void)testSetString { [customProperties setString:tooLongValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Empty value. // When @@ -148,8 +148,8 @@ - (void)testSetString { [customProperties setString:emptyValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(emptyValue)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(emptyValue)); // Normal value. // When @@ -157,8 +157,8 @@ - (void)testSetString { [customProperties setString:normalValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(normalValue)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(normalValue)); // Normal value with maximum length. // When @@ -166,8 +166,8 @@ - (void)testSetString { [customProperties setString:maxLongValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(maxLongValue)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(maxLongValue)); } - (void)testSetDate { @@ -179,13 +179,13 @@ - (void)testSetDate { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Null value. // When NSDate *nullValue = nil; [customProperties setDate:nullValue forKey:key]; - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Normal value. // When @@ -193,8 +193,8 @@ - (void)testSetDate { [customProperties setDate:normalValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(normalValue)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(normalValue)); } - (void)testSetNumber { @@ -206,7 +206,7 @@ - (void)testSetNumber { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Null value. // When @@ -214,7 +214,7 @@ - (void)testSetNumber { [customProperties setNumber:nullValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Normal value. // When @@ -222,8 +222,44 @@ - (void)testSetNumber { [customProperties setNumber:normalValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(normalValue)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(normalValue)); +} + +- (void)testSetInvalidNumber { + + // If + NSString *key = @"test"; + + // When + MSCustomProperties *customProperties = [MSCustomProperties new]; + + // Then + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); + + // NaN value. + // When + NSNumber *nanValue = [NSNumber numberWithDouble:NAN]; + [customProperties setNumber:nanValue forKey:key]; + + // Then + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); + + // Positive infinite value. + // When + NSNumber *positiveInfiniteValue = [NSNumber numberWithDouble:INFINITY]; + [customProperties setNumber:positiveInfiniteValue forKey:key]; + + // Then + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); + + // Negative infinite value. + // When + NSNumber *negativeInfiniteValue = [NSNumber numberWithDouble:-INFINITY]; + [customProperties setNumber:negativeInfiniteValue forKey:key]; + + // Then + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); } - (void)testSetBool { @@ -235,7 +271,7 @@ - (void)testSetBool { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // Normal value. // When @@ -243,8 +279,8 @@ - (void)testSetBool { [customProperties setBool:normalValue forKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is(@(normalValue))); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is(@(normalValue))); } - (void)testClear { @@ -256,14 +292,14 @@ - (void)testClear { MSCustomProperties *customProperties = [MSCustomProperties new]; // Then - assertThat([customProperties properties], hasCountOf(0)); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(0)); // When [customProperties clearPropertyForKey:key]; // Then - assertThat([customProperties properties], hasCountOf(1)); - assertThat([customProperties properties][key], is([NSNull null])); + assertThat([customProperties propertiesImmutableCopy], hasCountOf(1)); + assertThat([customProperties propertiesImmutableCopy][key], is([NSNull null])); } @end diff --git a/AppCenter/AppCenterTests/MSDBStorageTests.m b/AppCenter/AppCenterTests/MSDBStorageTests.m index f0a866a3a2..b5182f27c2 100644 --- a/AppCenter/AppCenterTests/MSDBStorageTests.m +++ b/AppCenter/AppCenterTests/MSDBStorageTests.m @@ -11,6 +11,9 @@ static NSString *const kMSTestMealColName = @"meal"; static NSString *const kMSTestDBFileName = @"Test.sqlite"; +// 40 KiB (10 pages by 4 KiB). +static const long kMSTestStorageSizeMinimumUpperLimitInBytes = 40 * 1024; + @interface MSDBStorageTests : XCTestCase @property(nonatomic) MSDBStorage *sut; @@ -300,15 +303,14 @@ - (void)testCount { - (void)testSetStorageSizeFailsWhenShrinkingDatabaseIsAttempted { // If - long initialSizeInBytes = kMSDefaultPageSizeInBytes * 10; XCTestExpectation *expectation = [self expectationWithDescription:@"Completion handler invoked."]; // Fill the database with data to reach the desired initial size. - while ([self.storageTestUtil getDataLengthInBytes] < initialSizeInBytes) { + while ([self.storageTestUtil getDataLengthInBytes] < kMSTestStorageSizeMinimumUpperLimitInBytes) { [self addGuysToTheTableWithCount:1000]; } long bytesOfData = [self.storageTestUtil getDataLengthInBytes]; - long shrunkenSizeInBytes = bytesOfData - kMSDefaultPageSizeInBytes * 3; + long shrunkenSizeInBytes = bytesOfData - 12 * 1024; // When __weak typeof(self) weakSelf = self; @@ -332,16 +334,15 @@ - (void)testSetStorageSizeFailsWhenShrinkingDatabaseIsAttempted { - (void)testSetStorageSizePassesWhenSizeIsGreaterThanCurrentBytesOfActualData { // If - long initialSizeInBytes = kMSDefaultPageSizeInBytes * 10; XCTestExpectation *expectation = [self expectationWithDescription:@"Completion handler invoked."]; // Fill the database with data to reach the desired initial size. - while ([self.storageTestUtil getDataLengthInBytes] < initialSizeInBytes) { + while ([self.storageTestUtil getDataLengthInBytes] < kMSTestStorageSizeMinimumUpperLimitInBytes) { [self addGuysToTheTableWithCount:1000]; } long bytesOfData = [self.storageTestUtil getDataLengthInBytes]; NSLog(@"bytes of data: %ld", bytesOfData); - long expandedSizeInBytes = bytesOfData + kMSDefaultPageSizeInBytes * 3; + long expandedSizeInBytes = bytesOfData + 12 * 1024; // When [self.sut setMaxStorageSize:expandedSizeInBytes @@ -369,16 +370,15 @@ - (void)testSetStorageSizePassesWhenSizeIsGreaterThanCurrentBytesOfActualData { - (void)testMaximumPageCountDoesNotChangeWhenShrinkingDatabaseIsAttempted { // If - __block const int initialMaxPageCount = self.sut.maxPageCount; - long initialSizeInBytes = kMSDefaultPageSizeInBytes * 10; + const long initialMaxSize = self.sut.maxSizeInBytes; XCTestExpectation *expectation = [self expectationWithDescription:@"Completion handler invoked."]; // Fill the database with data to reach the desired initial size. - while ([self.storageTestUtil getDataLengthInBytes] < initialSizeInBytes) { + while ([self.storageTestUtil getDataLengthInBytes] < kMSTestStorageSizeMinimumUpperLimitInBytes) { [self addGuysToTheTableWithCount:1000]; } long bytesOfData = [self.storageTestUtil getDataLengthInBytes]; - long shrunkenSizeInBytes = bytesOfData - kMSDefaultPageSizeInBytes * 3; + long shrunkenSizeInBytes = bytesOfData - 12 * 1024; // When __weak typeof(self) weakSelf = self; @@ -387,7 +387,7 @@ - (void)testMaximumPageCountDoesNotChangeWhenShrinkingDatabaseIsAttempted { // Then typeof(self) strongSelf = weakSelf; - XCTAssertEqual(initialMaxPageCount, strongSelf.sut.maxPageCount); + XCTAssertEqual(initialMaxSize, strongSelf.sut.maxSizeInBytes); [expectation fulfill]; }]; @@ -403,22 +403,13 @@ - (void)testMaximumPageCountDoesNotChangeWhenShrinkingDatabaseIsAttempted { - (void)testCompletionHandlerCanBeNil { // When - [self.sut setMaxStorageSize:kMSDefaultPageSizeInBytes completionHandler:nil]; + [self.sut setMaxStorageSize:4 * 1024 completionHandler:nil]; [self addGuysToTheTableWithCount:100]; // Then // Didn't crash. } -- (void)testDefaultDatabaseSize { - - // If - long expectedPageCount = kMSDefaultDatabaseSizeInBytes / kMSDefaultPageSizeInBytes; - - // Then - XCTAssertEqual(self.sut.maxPageCount, expectedPageCount); -} - - (void)testNewDatabaseIsAutoVacuumed { // Then diff --git a/AppCenter/AppCenterTests/MSDeadLockTests.m b/AppCenter/AppCenterTests/MSDeadLockTests.m index 3283e319a3..57f6800b78 100644 --- a/AppCenter/AppCenterTests/MSDeadLockTests.m +++ b/AppCenter/AppCenterTests/MSDeadLockTests.m @@ -39,9 +39,7 @@ - (NSString *)groupId { return @"service1"; } -- (void)channel:(id)__unused channel - didPrepareLog:(id)__unused log - withInternalId:(NSString *)__unused internalId { +- (void)channel:(id)channel didPrepareLog:(id)log internalId:(NSString *)internalId flags:(MSFlags)flags { // Operation locking AC while in ChannelDelegate. NSUUID *__unused deviceId = [MSAppCenter installId]; @@ -54,9 +52,8 @@ - (void)startWithChannelGroup:(id)channelGroup id mockLog = OCMPartialMock([MSAbstractLog new]); OCMStub([mockLog isValid]).andReturn(YES); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // Log enqueued from background thread (i.e. crash logs). - [self.channelUnit enqueueItem:mockLog]; + [self.channelUnit enqueueItem:mockLog flags:MSFlagsDefault]; }); } @@ -98,9 +95,8 @@ - (void)testDeadLockAtStartup { // When dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - // Start the SDK with interlocking sensible services. - [MSAppCenter start:@"AppSecret" withServices:@[ [MSDummyService1 class], [MSDummyService2 class] ]]; + [MSAppCenter start:@"AppSecret" withServices:@ [[MSDummyService1 class], [MSDummyService2 class]]]; [expectation fulfill]; }); diff --git a/AppCenter/AppCenterTests/MSLogDBStorageTests.m b/AppCenter/AppCenterTests/MSLogDBStorageTests.m index 120f336182..4bd87a9874 100644 --- a/AppCenter/AppCenterTests/MSLogDBStorageTests.m +++ b/AppCenter/AppCenterTests/MSLogDBStorageTests.m @@ -4,6 +4,7 @@ #import "MSDBStoragePrivate.h" #import "MSLogDBStoragePrivate.h" #import "MSLogDBStorageVersion.h" +#import "MSLogWithProperties.h" #import "MSStorageTestUtil.h" #import "MSTestFrameworks.h" #import "MSUtility.h" @@ -11,8 +12,8 @@ static NSString *const kMSTestGroupId = @"TestGroupId"; static NSString *const kMSAnotherTestGroupId = @"AnotherGroupId"; -// 40 KiB (10 pages). -static const long kMSTestStorageSizeMinimumUpperLimitInBytes = 10 * kMSDefaultPageSizeInBytes; +// 40 KiB (10 pages by 4 KiB). +static const long kMSTestStorageSizeMinimumUpperLimitInBytes = 40 * 1024; @interface MSLogDBStorageTests : XCTestCase @@ -24,12 +25,20 @@ @interface MSLogDBStorageTests : XCTestCase @implementation MSLogDBStorageTests #pragma mark - Setup + - (void)setUp { [super setUp]; self.storageTestUtil = [[MSStorageTestUtil alloc] initWithDbFileName:kMSDBFileName]; [self.storageTestUtil deleteDatabase]; XCTAssertEqual([self.storageTestUtil getDataLengthInBytes], 0); - self.sut = [MSLogDBStorage new]; + self.sut = OCMPartialMock([MSLogDBStorage new]); + OCMStub([self.sut executeNonSelectionQuery:OCMOCK_ANY]) + .andDo(^(NSInvocation *invocation) { + NSString *query; + [invocation getArgument:&query atIndex:2]; + [self validateQuerySyntax:query]; + }) + .andForwardToRealObject(); } - (void)tearDown { @@ -41,7 +50,10 @@ - (void)testLoadTooManyLogs { // If NSUInteger expectedLogsCount = 5; - NSMutableArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount + 1 groupId:kMSTestGroupId] mutableCopy]; + NSMutableArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount + 1 + groupId:kMSTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES] mutableCopy]; [expectedLogs removeLastObject]; // When @@ -49,7 +61,6 @@ - (void)testLoadTooManyLogs { limit:expectedLogsCount excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Then assertThat(batchId, notNilValue()); assertThat(expectedLogs, is(logArray)); @@ -57,18 +68,66 @@ - (void)testLoadTooManyLogs { XCTAssertTrue(moreLogsAvailable); } -- (void)testLoadJustEnoughLogs { +- (void)testLoadJustEnoughNormalLogs { // If NSUInteger expectedLogsCount = 5; - NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount groupId:kMSTestGroupId]; + NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount + groupId:kMSTestGroupId + flags:MSFlagsPersistenceNormal + andVerifyLogGeneration:YES]; // When BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId limit:expectedLogsCount excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { + // Then + assertThat(batchId, notNilValue()); + assertThat(expectedLogs, is(logArray)); + }]; + XCTAssertFalse(moreLogsAvailable); +} +- (void)testLoadJustEnoughMixedPriorityLogs { + + // If + NSUInteger segmentLogCount = 2; + NSUInteger expectedLogsCount = segmentLogCount * 4; + NSMutableArray *expectedLogs = [NSMutableArray new]; + + // Create 2 normal logs. + NSMutableArray *normalLogs = [[self generateAndSaveLogsWithCount:segmentLogCount + groupId:kMSTestGroupId + flags:MSFlagsPersistenceNormal + andVerifyLogGeneration:YES] mutableCopy]; + + // Create 2 critical logs. + expectedLogs = [[expectedLogs arrayByAddingObjectsFromArray:[self generateAndSaveLogsWithCount:segmentLogCount + groupId:kMSTestGroupId + flags:MSFlagsPersistenceCritical + andVerifyLogGeneration:YES]] mutableCopy]; + + // Create 2 normal logs. + normalLogs = [[normalLogs arrayByAddingObjectsFromArray:[self generateAndSaveLogsWithCount:segmentLogCount + groupId:kMSTestGroupId + flags:MSFlagsPersistenceNormal + andVerifyLogGeneration:NO]] mutableCopy]; + + // Create 2 critical logs. + expectedLogs = [[expectedLogs arrayByAddingObjectsFromArray:[self generateAndSaveLogsWithCount:segmentLogCount + groupId:kMSTestGroupId + flags:MSFlagsPersistenceCritical + andVerifyLogGeneration:NO]] mutableCopy]; + + // Build expected logs + expectedLogs = [[expectedLogs arrayByAddingObjectsFromArray:normalLogs] mutableCopy]; + + // When + BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId + limit:expectedLogsCount + excludedTargetKeys:nil + completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { // Then assertThat(batchId, notNilValue()); assertThat(expectedLogs, is(logArray)); @@ -81,14 +140,16 @@ - (void)testLoadNotEnoughLogs { // If NSUInteger expectedLogsCount = 2; NSUInteger limit = 5; - NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount groupId:kMSTestGroupId]; + NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount + groupId:kMSTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES]; // When BOOL moreLogsAvailable = [self.sut loadLogsWithGroupId:kMSTestGroupId limit:limit excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Then assertThat(batchId, notNilValue()); assertThat(expectedLogs, is(logArray)); @@ -100,7 +161,10 @@ - (void)testLoadLogsWhilePendingBatchesFromSameGroupId { // If NSUInteger expectedLogsCount = 5; - __block NSArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount groupId:kMSTestGroupId] mutableCopy]; + __block NSArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount + groupId:kMSTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES] mutableCopy]; __block NSArray *unexpectedLogs; __block NSString *unexpectedBatchId; @@ -109,7 +173,6 @@ - (void)testLoadLogsWhilePendingBatchesFromSameGroupId { limit:2 excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Those values shouldn't be in the next batch. unexpectedLogs = logArray; unexpectedBatchId = batchId; @@ -120,7 +183,6 @@ - (void)testLoadLogsWhilePendingBatchesFromSameGroupId { limit:expectedLogsCount excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Then // Logs from previous batch are not expected here. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", unexpectedLogs]; @@ -140,40 +202,39 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithSpecificTargetKeys { MSCommonSchemaLog *log1 = [MSCommonSchemaLog new]; [log1 addTransmissionTargetToken:@"1-t"]; log1.iKey = @"o:1"; - [self.sut saveLog:log1 withGroupId:kMSTestGroupId]; + [self.sut saveLog:log1 withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // Key: 2, group: A. MSCommonSchemaLog *log2 = [MSCommonSchemaLog new]; [log2 addTransmissionTargetToken:@"2-t"]; log2.iKey = @"o:2"; - [self.sut saveLog:log2 withGroupId:kMSTestGroupId]; + [self.sut saveLog:log2 withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // Key: 2, group: B. MSCommonSchemaLog *log3 = [MSCommonSchemaLog new]; [log3 addTransmissionTargetToken:@"2-t"]; log3.iKey = @"o:2"; - [self.sut saveLog:log3 withGroupId:kMSAnotherTestGroupId]; + [self.sut saveLog:log3 withGroupId:kMSAnotherTestGroupId flags:MSFlagsDefault]; // Key: 1, group: A. MSCommonSchemaLog *log4 = [MSCommonSchemaLog new]; [log4 addTransmissionTargetToken:@"1-t"]; log4.iKey = @"o:1"; - [self.sut saveLog:log4 withGroupId:kMSTestGroupId]; + [self.sut saveLog:log4 withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // Key: 2, group: A. MSCommonSchemaLog *log5 = [MSCommonSchemaLog new]; [log5 addTransmissionTargetToken:@"2-t"]; log5.iKey = @"o:2"; - [self.sut saveLog:log5 withGroupId:kMSTestGroupId]; + [self.sut saveLog:log5 withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // When [self.sut loadLogsWithGroupId:kMSTestGroupId limit:10 excludedTargetKeys:@[ @"1" ] completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { - // Then - assertThatInt([logArray count], equalToInt(2)); + assertThatInt(logArray.count, equalToInt(2)); for (MSCommonSchemaLog *log in logArray) { XCTAssertTrue([log.iKey isEqualToString:@"o:2"]); } @@ -182,9 +243,8 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithSpecificTargetKeys { limit:10 excludedTargetKeys:@[ @"2" ] completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { - // Then - assertThatInt([logArray count], equalToInt(2)); + assertThatInt(logArray.count, equalToInt(2)); for (MSCommonSchemaLog *log in logArray) { XCTAssertTrue([log.iKey isEqualToString:@"o:1"]); } @@ -193,9 +253,8 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithSpecificTargetKeys { limit:10 excludedTargetKeys:nil completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { - // Then - assertThatInt([logArray count], equalToInt(0)); + assertThatInt(logArray.count, equalToInt(0)); }]; } @@ -212,7 +271,7 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithTargetKeysForBackwardComp NSString *targetToken = [targetKey stringByAppendingString:@"-secret"]; [log addTransmissionTargetToken:targetToken]; } - [self.sut saveLog:log withGroupId:kMSTestGroupId]; + [self.sut saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } // Then @@ -220,7 +279,7 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithTargetKeysForBackwardComp limit:20 excludedTargetKeys:@[ @"testTargetKey1", @"testTargetKey2" ] completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { - assertThatInt([logArray count], equalToInt(5)); + assertThatInt(logArray.count, equalToInt(5)); }]; } @@ -237,7 +296,7 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithoutTargetKeysForBackwardC [log addTransmissionTargetToken:targetToken]; log.iKey = targetKey; } - [self.sut saveLog:log withGroupId:kMSTestGroupId]; + [self.sut saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } // Then @@ -252,7 +311,7 @@ - (void)testLoadCommonSchemaLogsWhilePendingBatchesWithoutTargetKeysForBackwardC } } XCTAssertEqual(iKeyCount, 5); - XCTAssertEqual([logArray count], 10); + XCTAssertEqual(logArray.count, 10); }]; } @@ -260,7 +319,10 @@ - (void)testLoadLogsWhilePendingBatchesFromOtherGroupId { // If NSUInteger expectedLogsCount = 5; - __block NSArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount groupId:kMSTestGroupId] mutableCopy]; + __block NSArray *expectedLogs = [[self generateAndSaveLogsWithCount:expectedLogsCount + groupId:kMSTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES] mutableCopy]; __block NSArray *unexpectedLogs; __block NSString *unexpectedBatchId; @@ -269,7 +331,6 @@ - (void)testLoadLogsWhilePendingBatchesFromOtherGroupId { limit:2 excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Those values shouldn't be in the next batch. unexpectedLogs = logArray; unexpectedBatchId = batchId; @@ -280,7 +341,6 @@ - (void)testLoadLogsWhilePendingBatchesFromOtherGroupId { limit:expectedLogsCount excludedTargetKeys:nil completionHandler:^(NSArray> *_Nonnull logArray, NSString *_Nonnull batchId) { - // Then // Logs from previous batch are not expected here. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"NOT (SELF IN %@)", unexpectedLogs]; @@ -296,7 +356,10 @@ - (void)testLoadUnlimitedLogs { // If NSUInteger expectedLogsCount = 42; - NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount groupId:kMSTestGroupId]; + NSArray *expectedLogs = [self generateAndSaveLogsWithCount:expectedLogsCount + groupId:kMSTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES]; // When NSArray *logs = [self.sut logsFromDBWithGroupId:kMSTestGroupId]; @@ -312,7 +375,7 @@ - (void)testDeleteLogsWithGroupId { // If self.sut = [MSLogDBStorage new]; // [self.sut.batches removeAllObjects]; - [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; // When [self.sut deleteLogsWithGroupId:kMSTestGroupId]; @@ -325,7 +388,7 @@ - (void)testDeleteLogsWithGroupId { // If // Generate logs and create one batch by loading logs. - [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil completionHandler:nil]; // When @@ -339,7 +402,7 @@ - (void)testDeleteLogsWithGroupId { // If // Generate logs and create two batches by loading logs twice. - [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil completionHandler:nil]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil completionHandler:nil]; @@ -355,8 +418,11 @@ - (void)testDeleteLogsWithGroupId { // If // Generate logs and create two batches of different group Ids. __block NSString *batchIdToDelete; - [self generateAndSaveLogsWithCount:2 groupId:kMSTestGroupId]; - NSArray *expectedLogs = [self generateAndSaveLogsWithCount:3 groupId:kMSAnotherTestGroupId]; + [self generateAndSaveLogsWithCount:2 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; + NSArray *expectedLogs = [self generateAndSaveLogsWithCount:3 + groupId:kMSAnotherTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil @@ -383,7 +449,7 @@ - (void)testDeleteLogsByBatchIdWithOnlyOnePendingBatch { NSString *condition; NSArray *remainingLogs; [self.sut.batches removeAllObjects]; - NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil @@ -413,7 +479,7 @@ - (void)testDeleteLogsByBatchIdWithMultiplePendingBatches { NSString *condition; NSArray *remainingLogs; [self.sut.batches removeAllObjects]; - NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil @@ -448,8 +514,11 @@ - (void)testDeleteLogsByBatchIdWithPendingBatchesFromOtherGroups { NSString *condition; NSArray *remainingLogs; [self.sut.batches removeAllObjects]; - NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; - NSArray *savedLogsFromOtherGroup = [self generateAndSaveLogsWithCount:3 groupId:kMSAnotherTestGroupId]; + NSArray *savedLogs = [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; + NSArray *savedLogsFromOtherGroup = [self generateAndSaveLogsWithCount:3 + groupId:kMSAnotherTestGroupId + flags:MSFlagsDefault + andVerifyLogGeneration:YES]; [self.sut loadLogsWithGroupId:kMSTestGroupId limit:2 excludedTargetKeys:nil @@ -487,7 +556,7 @@ - (void)testCommonSchemaLogTargetTokenIsSavedAndRestored { [log addTransmissionTargetToken:testTargetToken]; // When - [self.sut saveLog:log withGroupId:kMSTestGroupId]; + [self.sut saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // Then [self.sut loadLogsWithGroupId:kMSTestGroupId @@ -496,7 +565,7 @@ - (void)testCommonSchemaLogTargetTokenIsSavedAndRestored { completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { id restoredLog = logArray[0]; NSString *restoredTargetToken = [[restoredLog transmissionTargetTokens] anyObject]; - assertThatInt([[restoredLog transmissionTargetTokens] count], equalToInt(1)); + assertThatInt([restoredLog transmissionTargetTokens].count, equalToInt(1)); XCTAssertEqualObjects(testTargetToken, restoredTargetToken); }]; } @@ -509,14 +578,14 @@ - (void)testOnlyCommonSchemaLogTargetTokenIsSavedAndRestored { [log addTransmissionTargetToken:testTargetToken]; // When - [self.sut saveLog:log withGroupId:kMSTestGroupId]; + [self.sut saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; // Then [self.sut loadLogsWithGroupId:kMSTestGroupId limit:1 excludedTargetKeys:nil completionHandler:^(NSArray *_Nonnull logArray, __unused NSString *batchId) { - assertThatInt([[logArray[0] transmissionTargetTokens] count], equalToInt(0)); + assertThatInt([logArray[0] transmissionTargetTokens].count, equalToInt(0)); }]; } @@ -524,7 +593,7 @@ - (void)testDeleteLogsByBatchIdWithNoPendingBatches { // If [self.sut.batches removeAllObjects]; - [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId]; + [self generateAndSaveLogsWithCount:5 groupId:kMSTestGroupId flags:MSFlagsDefault andVerifyLogGeneration:YES]; // When [self.sut deleteLogsWithBatchId:MS_UUID_STRING groupId:kMSTestGroupId]; @@ -537,11 +606,11 @@ - (void)testDeleteLogsByBatchIdWithNoPendingBatches { - (void)testAddLogsWhenBelowStorageCapacity { // If - long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + kMSDefaultPageSizeInBytes; - long initialDataLengthInBytes = maxCapacityInBytes - 3 * kMSDefaultPageSizeInBytes; + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + long initialDataLengthInBytes = maxCapacityInBytes - 12 * 1024; MSAbstractLog *additionalLog = [MSAbstractLog new]; additionalLog.sid = MS_UUID_STRING; - NSArray *addedLogs = [self fillDatabaseWithLogsOfSizeInBytes:initialDataLengthInBytes]; + NSArray *addedDbIds = [self fillDatabaseWithLogsOfSizeInBytes:initialDataLengthInBytes ofPriority:MSFlagsPersistenceNormal]; // When [self.sut setMaxStorageSize:maxCapacityInBytes @@ -549,7 +618,7 @@ - (void)testAddLogsWhenBelowStorageCapacity { }]; // Then - BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsDefault]; // Then XCTAssertTrue(logSavedSuccessfully); @@ -558,14 +627,54 @@ - (void)testAddLogsWhenBelowStorageCapacity { NSArray> *allLogs = [self loadLogsWhere:nil]; XCTAssertEqual(loadedLogs.count, 1); XCTAssertEqualObjects(loadedLogs[0].sid, additionalLog.sid); - XCTAssertEqual(addedLogs.count + 1, allLogs.count); + XCTAssertEqual(addedDbIds.count + 1, allLogs.count); +} + +- (void)testAddCriticalLog { + + // If + MSAbstractLog *aLog = [MSAbstractLog new]; + aLog.sid = MS_UUID_STRING; + NSString *criticalLogsFilter = + [NSString stringWithFormat:@"\"%@\" = '%u'", kMSPriorityColumnName, (unsigned int)MSFlagsPersistenceCritical]; + NSString *normalLogsFilter = [NSString stringWithFormat:@"\"%@\" = '%u'", kMSPriorityColumnName, (unsigned int)MSFlagsPersistenceNormal]; + + // When + [self.sut saveLog:aLog withGroupId:kMSTestGroupId flags:MSFlagsPersistenceCritical]; + + // Then + NSArray> *criticalLogs = [self loadLogsWhere:criticalLogsFilter]; + NSArray> *normalLogs = [self loadLogsWhere:normalLogsFilter]; + XCTAssertEqual(criticalLogs.count, 1); + XCTAssertEqualObjects(criticalLogs[0].sid, aLog.sid); + XCTAssertEqual(normalLogs.count, 0); +} + +- (void)testAddNormalLog { + + // If + MSAbstractLog *aLog = [MSAbstractLog new]; + aLog.sid = MS_UUID_STRING; + NSString *criticalLogsFilter = + [NSString stringWithFormat:@"\"%@\" = '%u'", kMSPriorityColumnName, (unsigned int)MSFlagsPersistenceCritical]; + NSString *normalLogsFilter = [NSString stringWithFormat:@"\"%@\" = '%u'", kMSPriorityColumnName, (unsigned int)MSFlagsPersistenceNormal]; + + // When + [self.sut saveLog:aLog withGroupId:kMSTestGroupId flags:MSFlagsPersistenceNormal]; + + // Then + NSArray> *criticalLogs = [self loadLogsWhere:criticalLogsFilter]; + NSArray> *normalLogs = [self loadLogsWhere:normalLogsFilter]; + XCTAssertEqual(normalLogs.count, 1); + XCTAssertEqualObjects(normalLogs[0].sid, aLog.sid); + XCTAssertEqual(criticalLogs.count, 0); } - (void)testAddLogsDoesNotExceedCapacity { // If long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes; - [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes]; + [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes ofPriority:MSFlagsPersistenceNormal]; [self.sut setMaxStorageSize:maxCapacityInBytes completionHandler:^(__unused BOOL success){ }]; @@ -574,8 +683,7 @@ - (void)testAddLogsDoesNotExceedCapacity { int additionalLogs = 0; while (additionalLogs <= 50) { MSAbstractLog *additionalLog = [MSAbstractLog new]; - additionalLog.sid = MS_UUID_STRING; - BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSTestGroupId]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSTestGroupId flags:MSFlagsDefault]; ++additionalLogs; // Then @@ -584,39 +692,207 @@ - (void)testAddLogsDoesNotExceedCapacity { } } -- (void)testOldestLogsAreDeletedFirstWhenCapacityIsReached { +- (void)testSaveNormalPriorityLogPurgesOldestNormalPriorityLogsWhenStorageFull { // If - long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + kMSDefaultPageSizeInBytes; - NSArray *addedLogs = [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes]; - MSAbstractLog *firstLog = addedLogs[0]; - int initialLogCount = (int)[addedLogs count]; - __block int originalLogsCount = initialLogCount; + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + NSArray *addedDbIds = [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes ofPriority:MSFlagsPersistenceNormal]; + NSNumber *firstLogDbId = addedDbIds[0]; // When [self.sut setMaxStorageSize:maxCapacityInBytes completionHandler:^(__unused BOOL success){ }]; - while (originalLogsCount < initialLogCount) { - MSAbstractLog *additionalLog = [MSAbstractLog new]; - additionalLog.sid = MS_UUID_STRING; - BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId]; - NSString *originalLogsFilter = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSTestGroupId]; - NSArray> *originalLogs = [self loadLogsWhere:originalLogsFilter]; - originalLogsCount = (int)[originalLogs count]; - if (originalLogsCount < initialLogCount) { - XCTAssertEqual(originalLogsCount, initialLogCount - 1); - BOOL containsFirstLog = [self logs:originalLogs containLogWithSessionId:firstLog.sid]; - XCTAssertFalse(containsFirstLog); - } + MSAbstractLog *additionalLog = [MSAbstractLog new]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceNormal]; - // Then - XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); - XCTAssertTrue(logSavedSuccessfully); + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertTrue(logSavedSuccessfully); + XCTAssertFalse([self containsLogWithDbId:firstLogDbId]); + + NSString *whereCondition = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSAnotherTestGroupId]; + NSArray> *loadedLogs = [self loadLogsWhere:whereCondition]; + XCTAssertEqual(loadedLogs.count, 1); + XCTAssertEqualObjects(loadedLogs[0].sid, additionalLog.sid); + XCTAssertEqual(1, [self findUnknownDBIdsFromKnownIdList:addedDbIds].count); +} + +- (void)testSaveCriticalPriorityLogPurgesOldestNormalPriorityLogsWhenStorageFull { + + // If + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + NSArray *addedDbIds = [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes ofPriority:MSFlagsPersistenceNormal]; + NSNumber *firstLogDbId = addedDbIds[0]; + + // When + [self.sut setMaxStorageSize:maxCapacityInBytes + completionHandler:^(__unused BOOL success){ + }]; + MSAbstractLog *additionalLog = [MSAbstractLog new]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertTrue(logSavedSuccessfully); + XCTAssertFalse([self containsLogWithDbId:firstLogDbId]); + + NSString *whereCondition = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSAnotherTestGroupId]; + NSArray> *loadedLogs = [self loadLogsWhere:whereCondition]; + XCTAssertEqual(loadedLogs.count, 1); + XCTAssertEqualObjects(loadedLogs[0].sid, additionalLog.sid); + XCTAssertEqual(1, [self findUnknownDBIdsFromKnownIdList:addedDbIds].count); +} + +- (void)testSaveNormalPriorityLogDiscardsLogWhenStorageFullWithCriticalPriorityLogs { + + // If + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + NSArray *addedDbIds = [self fillDatabaseWithLogsOfSizeInBytes:maxCapacityInBytes ofPriority:MSFlagsPersistenceCritical]; + + // When + [self.sut setMaxStorageSize:maxCapacityInBytes + completionHandler:^(__unused BOOL success){ + }]; + MSAbstractLog *additionalLog = [MSAbstractLog new]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertFalse(logSavedSuccessfully); + for (NSNumber *dbId in addedDbIds) { + XCTAssertTrue([self containsLogWithDbId:dbId]); + } + + NSString *whereCondition = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSAnotherTestGroupId]; + NSArray> *loadedLogs = [self loadLogsWhere:whereCondition]; + XCTAssertEqual(loadedLogs.count, 0); + XCTAssertEqual(0, [self findUnknownDBIdsFromKnownIdList:addedDbIds].count); +} + +- (void)testSaveLogPurgesNormalPriorityLogWhenStorageFullWithMixedPriorityLogs { + + // If + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + NSDictionary *addedDbIds = [self fillDatabaseWithMixedPriorityLogsOfSizeInBytesAndReturnDbIds:maxCapacityInBytes]; + NSNumber *oldestCriticalDbId = [((NSArray *)[addedDbIds objectForKey:[NSNumber numberWithInt:MSFlagsPersistenceCritical]]) firstObject]; + NSNumber *oldestNormalDbId = [((NSArray *)[addedDbIds objectForKey:[NSNumber numberWithInt:MSFlagsPersistenceNormal]]) firstObject]; + + // When + [self.sut setMaxStorageSize:maxCapacityInBytes + completionHandler:^(__unused BOOL success){ + }]; + MSAbstractLog *additionalLog = [MSAbstractLog new]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertTrue(logSavedSuccessfully); + XCTAssertFalse([self containsLogWithDbId:oldestNormalDbId]); + XCTAssertTrue([self containsLogWithDbId:oldestCriticalDbId]); + NSString *whereCondition = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSAnotherTestGroupId]; + NSArray> *loadedLogs = [self loadLogsWhere:whereCondition]; + XCTAssertEqual(loadedLogs.count, 1); + XCTAssertEqualObjects(loadedLogs[0].sid, additionalLog.sid); + NSArray *knownIds = [NSArray new]; + for (NSArray *ids in [addedDbIds allValues]) { + knownIds = [knownIds arrayByAddingObjectsFromArray:ids]; + } + XCTAssertEqual(1, [self findUnknownDBIdsFromKnownIdList:knownIds].count); +} + +- (void)testSaveLargeNormalPriorityLogPurgesAllNormalPriorityLogs { + + // If + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + [self.sut setMaxStorageSize:maxCapacityInBytes + completionHandler:^(__unused BOOL success){ + }]; + [self generateAndSaveLogsWithCount:1 groupId:kMSTestGroupId flags:MSFlagsPersistenceCritical andVerifyLogGeneration:YES]; + [self generateAndSaveLogsWithCount:2 groupId:kMSTestGroupId flags:MSFlagsPersistenceNormal andVerifyLogGeneration:YES]; + id largeLog = [self generateLogWithSize:@(maxCapacityInBytes)]; + sqlite3 *db = [self.storageTestUtil openDatabase]; + NSArray *criticalDbIds = [self dbIdsForPriority:MSFlagsPersistenceCritical inOpenedDatabase:db]; + NSArray *normalDbIds = [self dbIdsForPriority:MSFlagsPersistenceNormal inOpenedDatabase:db]; + sqlite3_close(db); + + // When + BOOL logSavedSuccessfully = [self.sut saveLog:largeLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertFalse(logSavedSuccessfully); + NSString *whereCondition = [NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, kMSAnotherTestGroupId]; + NSArray> *loadedLogs = [self loadLogsWhere:whereCondition]; + XCTAssertEqual(loadedLogs.count, 0); + NSArray *knownIds = [NSArray new]; + for (NSArray *ids in @[ criticalDbIds, normalDbIds ]) { + knownIds = [knownIds arrayByAddingObjectsFromArray:ids]; + } + XCTAssertEqual(0, [self findUnknownDBIdsFromKnownIdList:knownIds].count); + for (NSNumber *dbId in normalDbIds) { + XCTAssertFalse([self containsLogWithDbId:dbId]); } + XCTAssertTrue([self containsLogWithDbId:[criticalDbIds firstObject]]); +} + +- (void)testSaveLargeCriticalPriorityLogPurgesAllLogs { + + // If + long maxCapacityInBytes = kMSTestStorageSizeMinimumUpperLimitInBytes + 4 * 1024; + [self.sut setMaxStorageSize:maxCapacityInBytes + completionHandler:^(__unused BOOL success){ + }]; + [self fillDatabaseWithMixedPriorityLogsOfSizeInBytesAndReturnDbIds:maxCapacityInBytes]; + id largeLog = [self generateLogWithSize:@(maxCapacityInBytes)]; + + // When + BOOL logSavedSuccessfully = [self.sut saveLog:largeLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertTrue([self.storageTestUtil getDataLengthInBytes] <= maxCapacityInBytes); + XCTAssertFalse(logSavedSuccessfully); + XCTAssertEqual(0, [self loadLogsWhere:nil].count); +} + +- (void)testErrorDeletingOldestLog { + + // If + id classMock = OCMClassMock([MSDBStorage class]); + OCMStub([classMock executeNonSelectionQuery:startsWith(@"INSERT") inOpenedDatabase:[OCMArg anyPointer]]).andReturn(SQLITE_FULL); + OCMStub([classMock executeNonSelectionQuery:startsWith(@"DELETE") inOpenedDatabase:[OCMArg anyPointer]]).andReturn(SQLITE_ERROR); + + // When + MSAbstractLog *additionalLog = [MSAbstractLog new]; + BOOL logSavedSuccessfully = [self.sut saveLog:additionalLog withGroupId:kMSAnotherTestGroupId flags:MSFlagsDefault]; + + // Then + XCTAssertFalse(logSavedSuccessfully); + [classMock stopMocking]; +} + +- (void)testCreateFromLatestSchema { + + // When + self.sut = [MSLogDBStorage new]; + + // Then + NSString *currentTable = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='%@'", kMSLogTableName]][0][0]; + assertThat(currentTable, is(@"CREATE TABLE \"logs\" (" + @"\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, " + @"\"groupId\" TEXT NOT NULL, " + @"\"log\" TEXT NOT NULL, " + @"\"targetToken\" TEXT, " + @"\"targetKey\" TEXT, " + @"\"priority\" INTEGER)")); + NSString *priorityIndex = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='ix_%@_%@'", kMSLogTableName, + kMSPriorityColumnName]][0][0]; + assertThat(priorityIndex, is(@"CREATE INDEX \"ix_logs_priority\" ON \"logs\" (\"priority\")")); } -- (void)testMigrationFromSchema0to2 { +- (void)testMigrationFromSchema0to3 { // If // Create old version db. @@ -629,7 +905,7 @@ - (void)testMigrationFromSchema0to2 { ] }; MSDBStorage *storage0 = [[MSDBStorage alloc] initWithSchema:schema0 version:kMSInitialVersion filename:kMSDBFileName]; - [self generateAndSaveLogsWithCount:10 groupId:kMSTestGroupId storage:storage0]; + [self generateAndSaveLogsWithCount:10 size:nil groupId:kMSTestGroupId flags:MSFlagsDefault storage:storage0 andVerifyLogGeneration:YES]; // When self.sut = [MSLogDBStorage new]; @@ -643,10 +919,15 @@ - (void)testMigrationFromSchema0to2 { @"\"groupId\" TEXT NOT NULL, " @"\"log\" TEXT NOT NULL, " @"\"targetToken\" TEXT, " - @"\"targetKey\" TEXT)")); + @"\"targetKey\" TEXT, " + @"\"priority\" INTEGER)")); + NSString *priorityIndex = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='ix_%@_%@'", kMSLogTableName, + kMSPriorityColumnName]][0][0]; + assertThat(priorityIndex, is(@"CREATE INDEX \"ix_logs_priority\" ON \"logs\" (\"priority\")")); } -- (void)testMigrationFromSchema1to2 { +- (void)testMigrationFromSchema1to3 { // If // Create old version db. @@ -659,7 +940,7 @@ - (void)testMigrationFromSchema1to2 { ] }; MSDBStorage *storage1 = [[MSDBStorage alloc] initWithSchema:schema1 version:kMSTargetTokenVersion filename:kMSDBFileName]; - [self generateAndSaveLogsWithCount:10 groupId:kMSTestGroupId storage:storage1]; + [self generateAndSaveLogsWithCount:10 size:nil groupId:kMSTestGroupId flags:MSFlagsDefault storage:storage1 andVerifyLogGeneration:YES]; // When self.sut = [MSLogDBStorage new]; @@ -673,36 +954,119 @@ - (void)testMigrationFromSchema1to2 { @"\"groupId\" TEXT NOT NULL, " @"\"log\" TEXT NOT NULL, " @"\"targetToken\" TEXT, " - @"\"targetKey\" TEXT)")); + @"\"targetKey\" TEXT, " + @"\"priority\" INTEGER)")); + NSString *priorityIndex = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='ix_%@_%@'", kMSLogTableName, + kMSPriorityColumnName]][0][0]; + assertThat(priorityIndex, is(@"CREATE INDEX \"ix_logs_priority\" ON \"logs\" (\"priority\")")); +} + +- (void)testMigrationFromSchema2to3 { + + // If + // Create old version db. + // DO NOT CHANGE. THIS IS ALREADY PUBLISHED SCHEMA. + MSDBSchema *schema2 = @{ + kMSLogTableName : @[ + @{kMSIdColumnName : @[ kMSSQLiteTypeInteger, kMSSQLiteConstraintPrimaryKey, kMSSQLiteConstraintAutoincrement ]}, + @{kMSGroupIdColumnName : @[ kMSSQLiteTypeText, kMSSQLiteConstraintNotNull ]}, + @{kMSLogColumnName : @[ kMSSQLiteTypeText, kMSSQLiteConstraintNotNull ]}, @{kMSTargetTokenColumnName : @[ kMSSQLiteTypeText ]}, + @{kMSTargetKeyColumnName : @[ kMSSQLiteTypeText ]} + ] + }; + MSDBStorage *storage2 = [[MSDBStorage alloc] initWithSchema:schema2 version:kMSTargetTokenVersion filename:kMSDBFileName]; + [self generateAndSaveLogsWithCount:10 size:nil groupId:kMSTestGroupId flags:MSFlagsDefault storage:storage2 andVerifyLogGeneration:YES]; + + // When + self.sut = [MSLogDBStorage new]; + + // Then + assertThatInt([self loadLogsWhere:nil].count, equalToUnsignedInt(10)); + NSString *currentTable = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='%@'", kMSLogTableName]][0][0]; + assertThat(currentTable, is(@"CREATE TABLE \"logs\" (" + @"\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, " + @"\"groupId\" TEXT NOT NULL, " + @"\"log\" TEXT NOT NULL, " + @"\"targetToken\" TEXT, " + @"\"targetKey\" TEXT, " + @"\"priority\" INTEGER)")); + NSString *priorityIndex = + [self.sut executeSelectionQuery:[NSString stringWithFormat:@"SELECT sql FROM sqlite_master WHERE name='ix_%@_%@'", kMSLogTableName, + kMSPriorityColumnName]][0][0]; + assertThat(priorityIndex, is(@"CREATE INDEX \"ix_logs_priority\" ON \"logs\" (\"priority\")")); } #pragma mark - Helper methods -- (NSArray> *)generateAndSaveLogsWithCount:(NSUInteger)count groupId:(NSString *)groupId { - return [self generateAndSaveLogsWithCount:count groupId:groupId storage:self.sut]; +- (id)generateLogWithSize:(NSNumber *)size { + MSLogWithProperties *log = [MSLogWithProperties new]; + if (size) { + NSString *s = [@"" stringByPaddingToLength:[size unsignedIntegerValue] withString:@"." startingAtIndex:0]; + log.properties = [NSMutableDictionary new]; + [log.properties setValue:s forKey:@"s"]; + } + log.sid = MS_UUID_STRING; + return log; +} + +- (NSArray> *)generateAndSaveLogsWithCount:(NSUInteger)count + groupId:(NSString *)groupId + flags:(MSFlags)flags + andVerifyLogGeneration:(BOOL)verify { + return [self generateAndSaveLogsWithCount:count size:nil groupId:groupId flags:flags storage:self.sut andVerifyLogGeneration:verify]; +} + +- (NSArray> *)generateAndSaveLogsWithCount:(NSUInteger)count + size:(NSNumber *)size + groupId:(NSString *)groupId + flags:(MSFlags)flags + andVerifyLogGeneration:(BOOL)verify { + return [self generateAndSaveLogsWithCount:count size:size groupId:groupId flags:flags storage:self.sut andVerifyLogGeneration:verify]; } -- (NSArray> *)generateAndSaveLogsWithCount:(NSUInteger)count groupId:(NSString *)groupId storage:(MSDBStorage *)storage { +- (NSArray> *)generateAndSaveLogsWithCount:(NSUInteger)count + size:(NSNumber *)size + groupId:(NSString *)groupId + flags:(MSFlags)flags + storage:(MSDBStorage *)storage + andVerifyLogGeneration:(BOOL)verify { NSMutableArray> *logs = [NSMutableArray arrayWithCapacity:count]; NSUInteger trueLogCount; for (NSUInteger i = 0; i < count; ++i) { - id log = [MSAbstractLog new]; - log.sid = MS_UUID_STRING; + id log = [self generateLogWithSize:size]; NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\") VALUES ('%@', '%@')", kMSLogTableName, - kMSGroupIdColumnName, kMSLogColumnName, groupId, base64Data]; + NSString *addLogQuery = + [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", \"%@\") VALUES ('%@', '%@', %u)", kMSLogTableName, + kMSGroupIdColumnName, kMSLogColumnName, kMSPriorityColumnName, groupId, base64Data, (unsigned int)flags]; [storage executeNonSelectionQuery:addLogQuery]; [logs addObject:log]; } - // Check the insertion worked. - trueLogCount = - [storage countEntriesForTable:kMSLogTableName condition:[NSString stringWithFormat:@"\"%@\" = '%@'", kMSGroupIdColumnName, groupId]]; - assertThatUnsignedInteger(trueLogCount, equalToUnsignedInteger(count)); + if (verify) { + + // Check the insertion worked. + trueLogCount = [storage countEntriesForTable:kMSLogTableName + condition:[NSString stringWithFormat:@"\"%@\" = '%@' AND \"%@\" = %u", kMSGroupIdColumnName, groupId, + kMSPriorityColumnName, (unsigned int)flags]]; + assertThatUnsignedInteger(trueLogCount, equalToUnsignedInteger(count)); + } return logs; } +- (NSArray *)dbIdsForPriority:(MSFlags)flags inOpenedDatabase:(void *)db { + NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT \"%@\" FROM \"%@\" WHERE \"%@\" = %u ORDER BY \"%@\" ASC", kMSIdColumnName, + kMSLogTableName, kMSPriorityColumnName, (unsigned int)flags, kMSIdColumnName]; + NSArray *entries = [MSDBStorage executeSelectionQuery:selectLogQuery inOpenedDatabase:db]; + NSMutableArray *ids = [NSMutableArray new]; + for (NSMutableArray *row in entries) { + [ids addObject:row[0]]; + } + return ids; +} + - (NSArray> *)loadLogsWhere:(nullable NSString *)whereCondition { NSMutableArray> *logs = [NSMutableArray> new]; NSMutableArray *rows = [NSMutableArray new]; @@ -747,37 +1111,107 @@ - (void)testMigrationFromSchema1to2 { return logs; } -- (NSArray> *)fillDatabaseWithLogsOfSizeInBytes:(long)sizeInBytes { - NSMutableArray *logsAdded = [NSMutableArray new]; +- (NSArray *)fillDatabaseWithLogsOfSizeInBytes:(long)sizeInBytes ofPriority:(MSFlags)priority { int result = 0; - int maxPageCount = (int)(sizeInBytes / kMSDefaultPageSizeInBytes); + sqlite3 *db = [self.storageTestUtil openDatabase]; + sqlite3_stmt *statement = NULL; + sqlite3_prepare_v2(db, "PRAGMA page_size;", -1, &statement, NULL); + sqlite3_step(statement); + int pageSize = sqlite3_column_int(statement, 0); + sqlite3_finalize(statement); + long maxPageCount = sizeInBytes / pageSize; + sqlite3_exec(db, [[NSString stringWithFormat:@"PRAGMA max_page_count = %ld;", maxPageCount] UTF8String], NULL, NULL, NULL); do { - sqlite3 *db = [self.storageTestUtil openDatabase]; - NSString *statement = [NSString stringWithFormat:@"PRAGMA max_page_count = %i;", maxPageCount]; - sqlite3_exec(db, [statement UTF8String], NULL, NULL, NULL); MSAbstractLog *log = [MSAbstractLog new]; - log.sid = MS_UUID_STRING; NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; - NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\") VALUES ('%@', '%@')", kMSLogTableName, - kMSGroupIdColumnName, kMSLogColumnName, kMSTestGroupId, base64Data]; + NSString *addLogQuery = [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", \"%@\") VALUES ('%@', '%@', %u)", + kMSLogTableName, kMSGroupIdColumnName, kMSLogColumnName, kMSPriorityColumnName, + kMSTestGroupId, base64Data, (unsigned int)priority]; result = sqlite3_exec(db, [addLogQuery UTF8String], NULL, NULL, NULL); - sqlite3_close(db); - if (result == SQLITE_OK) { - [logsAdded addObject:log]; - } } while (result == SQLITE_OK); - return logsAdded; + + // Get DB IDs for logs + NSString *selectLogQuery = + [NSString stringWithFormat:@"SELECT \"%@\" FROM \"%@\" ORDER BY \"%@\" ASC", kMSIdColumnName, kMSLogTableName, kMSIdColumnName]; + NSArray *entries = [MSDBStorage executeSelectionQuery:selectLogQuery inOpenedDatabase:db]; + NSMutableArray *ids = [NSMutableArray new]; + for (NSMutableArray *row in entries) { + [ids addObject:row[0]]; + } + sqlite3_close(db); + + return ids; } -- (BOOL)logs:(NSArray> *)logs containLogWithSessionId:(NSString *)sessionId { - for (MSAbstractLog *log in logs) { - if ([log.sid isEqualToString:sessionId]) { - return YES; - break; +- (NSDictionary *> *)fillDatabaseWithMixedPriorityLogsOfSizeInBytesAndReturnDbIds:(long)sizeInBytes { + int result = 0, count = 0; + sqlite3 *db = [self.storageTestUtil openDatabase]; + sqlite3_stmt *statement = NULL; + sqlite3_prepare_v2(db, "PRAGMA page_size;", -1, &statement, NULL); + sqlite3_step(statement); + int pageSize = sqlite3_column_int(statement, 0); + sqlite3_finalize(statement); + long maxPageCount = sizeInBytes / pageSize; + sqlite3_exec(db, [[NSString stringWithFormat:@"PRAGMA max_page_count = %ld;", maxPageCount] UTF8String], NULL, NULL, NULL); + do { + MSAbstractLog *log = [MSAbstractLog new]; + NSData *logData = [NSKeyedArchiver archivedDataWithRootObject:log]; + NSString *base64Data = [logData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; + NSString *addLogQuery = + [NSString stringWithFormat:@"INSERT INTO \"%@\" (\"%@\", \"%@\", \"%@\") VALUES ('%@', '%@', %u)", kMSLogTableName, + kMSGroupIdColumnName, kMSLogColumnName, kMSPriorityColumnName, kMSTestGroupId, base64Data, + (unsigned int)(count++ % 2 == 0 ? MSFlagsPersistenceCritical : MSFlagsPersistenceNormal)]; + result = sqlite3_exec(db, [addLogQuery UTF8String], NULL, NULL, NULL); + } while (result == SQLITE_OK); + + // Get DB IDs for logs + NSMutableDictionary *ids = [NSMutableDictionary new]; + for (NSNumber *flag in @[ [NSNumber numberWithInt:MSFlagsPersistenceNormal], [NSNumber numberWithInt:MSFlagsPersistenceCritical] ]) { + NSString *selectLogQuery = + [NSString stringWithFormat:@"SELECT \"%@\" FROM \"%@\" WHERE \"%@\" = %u ORDER BY \"%@\" ASC", kMSIdColumnName, kMSLogTableName, + kMSPriorityColumnName, (unsigned int)[flag unsignedIntegerValue], kMSIdColumnName]; + NSArray *entries = [MSDBStorage executeSelectionQuery:selectLogQuery inOpenedDatabase:db]; + NSMutableArray *priorityIds = [NSMutableArray new]; + for (NSMutableArray *row in entries) { + [priorityIds addObject:row[0]]; } + [ids setObject:priorityIds forKey:flag]; + } + sqlite3_close(db); + + return ids; +} + +- (BOOL)containsLogWithDbId:(NSNumber *)dbId { + sqlite3 *db = [self.storageTestUtil openDatabase]; + NSString *selectLogQuery = + [NSString stringWithFormat:@"SELECT COUNT(*) FROM \"%@\" WHERE \"%@\" = %@", kMSLogTableName, kMSIdColumnName, dbId]; + NSArray *> *entries = [MSDBStorage executeSelectionQuery:selectLogQuery inOpenedDatabase:db]; + if (entries.count > 0) { + return entries[0][0].unsignedIntegerValue > 0; } return NO; } +- (NSArray *)findUnknownDBIdsFromKnownIdList:(NSArray *)idList { + sqlite3 *db = [self.storageTestUtil openDatabase]; + NSString *selectLogQuery = [NSString stringWithFormat:@"SELECT \"%@\" FROM \"%@\" WHERE \"%@\" NOT IN (%@)", kMSIdColumnName, + kMSLogTableName, kMSIdColumnName, [idList componentsJoinedByString:@","]]; + NSArray *> *entries = [MSDBStorage executeSelectionQuery:selectLogQuery inOpenedDatabase:db]; + if (entries.count > 0) { + return entries[0]; + } + return nil; +} + +- (void)validateQuerySyntax:(NSString *)query { + sqlite3 *db = [self.storageTestUtil openDatabase]; + NSString *statement = [NSString stringWithFormat:@"EXPLAIN %@", query]; + char *error; + int result = sqlite3_exec(db, [statement UTF8String], NULL, NULL, &error); + XCTAssert(result == SQLITE_OK, "%s", error); + sqlite3_close(db); +} + @end diff --git a/AppCenter/AppCenterTests/MSLoggerTests.m b/AppCenter/AppCenterTests/MSLoggerTests.m index 116d5cc52c..a37aaff860 100644 --- a/AppCenter/AppCenterTests/MSLoggerTests.m +++ b/AppCenter/AppCenterTests/MSLoggerTests.m @@ -35,7 +35,7 @@ - (void)testDefaultLogLevels { XCTAssertTrue([MSLogger currentLogLevel] == MSLogLevelWarning); } -- (void)testLoglevels { +- (void)testSetLoglevels { // Check isUserDefinedLogLevel XCTAssertFalse([MSLogger isUserDefinedLogLevel]); @@ -48,4 +48,19 @@ - (void)testSetCurrentLoglevelWorks { XCTAssertTrue([MSLogger currentLogLevel] == MSLogLevelWarning); } +- (void)testLoglevelNoneDoesNotLogMessages { + + // If + MSLogMessageProvider messageProvider = ^() { + + // Then + XCTFail(@"Log shouldn't be printed."); + return @""; + }; + + // When + [MSLogger setCurrentLogLevel:MSLogLevelNone]; + [MSLogger logMessage:messageProvider level:MSLogLevelNone tag:@"TAG" file:nil function:nil line:0]; +} + @end diff --git a/AppCenter/AppCenterTests/MSModelTestsUtililty.h b/AppCenter/AppCenterTests/MSModelTestsUtililty.h index f63ead57fe..8b7a320e36 100644 --- a/AppCenter/AppCenterTests/MSModelTestsUtililty.h +++ b/AppCenter/AppCenterTests/MSModelTestsUtililty.h @@ -3,6 +3,7 @@ #import "MSAbstractLogInternal.h" #import "MSDevice.h" +@class MSMetadataExtension; @class MSUserExtension; @class MSLocExtension; @class MSOSExtension; @@ -28,6 +29,13 @@ */ + (NSMutableDictionary *)extensionDummies; +/** + * Get dummy values for common schema metadata extensions. + * + * @return Dummy values for common schema metadata extensions. + */ ++ (NSDictionary *)metadataExtensionDummies; + /** * Get dummy values for common schema user extensions. * @@ -114,6 +122,15 @@ */ + (MSCSExtensions *)extensionsWithDummyValues:(NSDictionary *)dummyValues; +/** + * Populate a dummy common schema user extension. + * + * @param dummyValues Dummy values to create the extension. + * + * @return A dummy common schema user extension. + */ ++ (MSMetadataExtension *)metadataExtensionWithDummyValues:(NSDictionary *)dummyValues; + /** * Populate a dummy common schema user extension. * diff --git a/AppCenter/AppCenterTests/MSModelTestsUtililty.m b/AppCenter/AppCenterTests/MSModelTestsUtililty.m index cbf3f9dba1..647269651d 100644 --- a/AppCenter/AppCenterTests/MSModelTestsUtililty.m +++ b/AppCenter/AppCenterTests/MSModelTestsUtililty.m @@ -1,3 +1,4 @@ +#import "MSModelTestsUtililty.h" #import "MSACModelConstants.h" #import "MSAppExtension.h" #import "MSCSData.h" @@ -6,7 +7,7 @@ #import "MSDeviceExtension.h" #import "MSDeviceInternal.h" #import "MSLocExtension.h" -#import "MSModelTestsUtililty.h" +#import "MSMetadataExtension.h" #import "MSNetExtension.h" #import "MSOSExtension.h" #import "MSProtocolExtension.h" @@ -77,40 +78,44 @@ + (NSMutableDictionary *)extensionDummies { } mutableCopy]; } ++ (NSDictionary *)metadataExtensionDummies { + return @{kMSFieldDelimiter : @{@"baseData" : @{kMSFieldDelimiter : @{@"screenSize" : @2}}}}; +} + + (NSDictionary *)userExtensionDummies { - return @{ kMSUserLocale : @"en-us" }; + return @{kMSUserLocale : @"en-us"}; } + (NSDictionary *)locExtensionDummies { - return @{ kMSTimezone : @"-03:00" }; + return @{kMSTimezone : @"-03:00"}; } + (NSDictionary *)osExtensionDummies { - return @{ kMSOSName : @"iOS", kMSOSVer : @"9.0" }; + return @{kMSOSName : @"iOS", kMSOSVer : @"9.0"}; } + (NSDictionary *)appExtensionDummies { - return @{ kMSAppId : @"com.some.bundle.id", kMSAppVer : @"3.4.1", kMSAppLocale : @"en-us" }; + return @{kMSAppId : @"com.some.bundle.id", kMSAppVer : @"3.4.1", kMSAppLocale : @"en-us"}; } + (NSDictionary *)protocolExtensionDummies { - return @{ kMSTicketKeys : @[ @"ticketKey1", @"ticketKey2" ], kMSDevMake : @"Apple", kMSDevModel : @"iPhone X" }; + return @{kMSTicketKeys : @[ @"ticketKey1", @"ticketKey2" ], kMSDevMake : @"Apple", kMSDevModel : @"iPhone X"}; } + (NSDictionary *)netExtensionDummies { - return @{ kMSNetProvider : @"Verizon" }; + return @{kMSNetProvider : @"Verizon"}; } + (NSMutableDictionary *)sdkExtensionDummies { - return [@{ kMSSDKLibVer : @"1.2.0", kMSSDKEpoch : MS_UUID_STRING, kMSSDKSeq : @1, kMSSDKInstallId : [NSUUID new] } mutableCopy]; + return [@{kMSSDKLibVer : @"1.2.0", kMSSDKEpoch : MS_UUID_STRING, kMSSDKSeq : @1, kMSSDKInstallId : [NSUUID new]} mutableCopy]; } + (NSMutableDictionary *)deviceExtensionDummies { - return [@{ kMSDeviceLocalId : @"00000000-0000-0000-0000-000000000000" } mutableCopy]; + return [@{kMSDeviceLocalId : @"00000000-0000-0000-0000-000000000000"} mutableCopy]; } + (NSDictionary *)dataDummies { - return @{ @"akey" : @"avalue", @"anested.key" : @"anothervalue", @"anotherkey" : @"yetanothervalue" }; + return @{@"akey" : @"avalue", @"anested.key" : @"anothervalue", @"anotherkey" : @"yetanothervalue"}; } + (MSDevice *)dummyDevice { @@ -232,6 +237,12 @@ + (MSDeviceExtension *)deviceExtensionWithDummyValues:(NSDictionary *)dummyValue return deviceExt; } ++ (MSMetadataExtension *)metadataExtensionWithDummyValues:(NSDictionary *)dummyValues { + MSMetadataExtension *metadataExt = [MSMetadataExtension new]; + metadataExt.metadata = dummyValues; + return metadataExt; +} + + (MSCSData *)dataWithDummyValues:(NSDictionary *)dummyValues { MSCSData *data = [MSCSData new]; data.properties = dummyValues; diff --git a/AppCenter/AppCenterTests/MSOneCollectorChannelDelegateTests.m b/AppCenter/AppCenterTests/MSOneCollectorChannelDelegateTests.m index d2fa5dfe2d..39e8319df6 100644 --- a/AppCenter/AppCenterTests/MSOneCollectorChannelDelegateTests.m +++ b/AppCenter/AppCenterTests/MSOneCollectorChannelDelegateTests.m @@ -7,8 +7,8 @@ #import "MSIngestionProtocol.h" #import "MSMockLogObject.h" #import "MSMockLogWithConversion.h" -#import "MSOneCollectorIngestion.h" #import "MSOneCollectorChannelDelegatePrivate.h" +#import "MSOneCollectorIngestion.h" #import "MSSDKExtension.h" #import "MSStorage.h" #import "MSTestFrameworks.h" @@ -233,24 +233,58 @@ - (void)testDidEnqueueLogToOneCollectorChannelWhenLogHasTargetTokensAndLogIsNotC [transmissionTargetTokens addObject:@"fake-transmission-target-token"]; MSCommonSchemaLog *commonSchemaLog = [MSCommonSchemaLog new]; id mockLog = OCMProtocolMock(@protocol(MSMockLogWithConversion)); - OCMStub([mockLog toCommonSchemaLogs]).andReturn(@[ commonSchemaLog ]); + OCMStub([mockLog toCommonSchemaLogsWithFlags:MSFlagsDefault]).andReturn(@[ commonSchemaLog ]); OCMStub(mockLog.transmissionTargetTokens).andReturn(transmissionTargetTokens); // When [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; - [self.sut channel:channelUnitMock didPrepareLog:mockLog withInternalId:@"fake-id"]; + [self.sut channel:channelUnitMock didPrepareLog:mockLog internalId:@"fake-id" flags:MSFlagsDefault]; // Then [self enqueueChannelEndJobExpectation]; [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - OCMVerify([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog]); + OCMVerify([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog flags:MSFlagsDefault]); if (error) { XCTFail(@"Expectation Failed with error: %@", error); } }]; } +- (void)testDidEnqueueLogToOneCollectorChannelSynchronously { + + // If + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([channelUnitMock configuration]).andReturn(self.baseUnitConfig); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + id oneCollectorChannelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub(oneCollectorChannelUnitMock.logsDispatchQueue).andReturn(self.logsDispatchQueue); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY withIngestion:OCMOCK_ANY]).andReturn(oneCollectorChannelUnitMock); + NSMutableSet *transmissionTargetTokens = [NSMutableSet new]; + [transmissionTargetTokens addObject:@"fake-transmission-target-token"]; + MSCommonSchemaLog *commonSchemaLog = [MSCommonSchemaLog new]; + id mockLog = OCMProtocolMock(@protocol(MSMockLogWithConversion)); + OCMStub([mockLog toCommonSchemaLogsWithFlags:MSFlagsDefault]).andReturn(@[ commonSchemaLog ]); + OCMStub(mockLog.transmissionTargetTokens).andReturn(transmissionTargetTokens); + dispatch_semaphore_t sem = dispatch_semaphore_create(0); + + /* + * Make sure that the common schema log is enqueued synchronously by putting a task on the log queue that won't return + * by the time verify is called. + */ + dispatch_async(oneCollectorChannelUnitMock.logsDispatchQueue, ^{ + dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); + }); + + // When + [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; + [self.sut channel:channelUnitMock didPrepareLog:mockLog internalId:@"fake-id" flags:MSFlagsDefault]; + + // Then + OCMVerify([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog flags:MSFlagsDefault]); + dispatch_semaphore_signal(sem); +} + - (void)testDidNotEnqueueLogToOneCollectorChannelWhenLogDoesNotConformToMSLogConversionProtocol { // If @@ -266,11 +300,11 @@ - (void)testDidNotEnqueueLogToOneCollectorChannelWhenLogDoesNotConformToMSLogCon OCMStub(mockLog.transmissionTargetTokens).andReturn(transmissionTargetTokens); // Then - OCMReject([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog]); + OCMReject([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog flags:MSFlagsDefault]); // When [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; - [self.sut channel:channelUnitMock didPrepareLog:mockLog withInternalId:@"fake-id"]; + [self.sut channel:channelUnitMock didPrepareLog:mockLog internalId:@"fake-id" flags:MSFlagsDefault]; } - (void)testReEnqueueLogWhenCommonSchemaLogIsPrepared { @@ -289,13 +323,13 @@ - (void)testReEnqueueLogWhenCommonSchemaLogIsPrepared { // When [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; - [self.sut channel:channelUnitMock didPrepareLog:commonSchemaLog withInternalId:@"fake-id"]; + [self.sut channel:channelUnitMock didPrepareLog:commonSchemaLog internalId:@"fake-id" flags:MSFlagsDefault]; // Then [self enqueueChannelEndJobExpectation]; [self waitForExpectationsWithTimeout:1 handler:^(NSError *error) { - OCMVerify([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog]); + OCMVerify([oneCollectorChannelUnitMock enqueueItem:commonSchemaLog flags:MSFlagsDefault]); if (error) { XCTFail(@"Expectation Failed with error: %@", error); } @@ -313,15 +347,15 @@ - (void)testDidNotEnqueueLogWhenLogHasNoTargetTokens { NSMutableSet *transmissionTargetTokens = [NSMutableSet new]; id mockLog = OCMProtocolMock(@protocol(MSMockLogWithConversion)); OCMStub(mockLog.transmissionTargetTokens).andReturn(transmissionTargetTokens); - OCMStub([mockLog toCommonSchemaLogs]).andReturn(@[ [MSCommonSchemaLog new] ]); + OCMStub([mockLog toCommonSchemaLogsWithFlags:MSFlagsDefault]).andReturn(@ [[MSCommonSchemaLog new]]); OCMStub([mockLog isKindOfClass:[MSCommonSchemaLog class]]).andReturn(NO); // Then - OCMReject([oneCollectorChannelUnitMock enqueueItem:OCMOCK_ANY]); + OCMReject([oneCollectorChannelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); // When [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; - [self.sut channel:channelUnitMock didPrepareLog:mockLog withInternalId:@"fake-id"]; + [self.sut channel:channelUnitMock didPrepareLog:mockLog internalId:@"fake-id" flags:MSFlagsDefault]; } - (void)testDidNotEnqueueLogWhenLogHasNilTargetTokens { @@ -335,14 +369,14 @@ - (void)testDidNotEnqueueLogWhenLogHasNilTargetTokens { id mockLog = OCMProtocolMock(@protocol(MSMockLogWithConversion)); OCMStub([mockLog isKindOfClass:[MSCommonSchemaLog class]]).andReturn(NO); OCMStub(mockLog.transmissionTargetTokens).andReturn(nil); - OCMStub([mockLog toCommonSchemaLogs]).andReturn(@[ [MSCommonSchemaLog new] ]); + OCMStub([mockLog toCommonSchemaLogsWithFlags:MSFlagsDefault]).andReturn(@ [[MSCommonSchemaLog new]]); // Then - OCMReject([oneCollectorChannelUnitMock enqueueItem:OCMOCK_ANY]); + OCMReject([oneCollectorChannelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); // When [self.sut channelGroup:channelGroupMock didAddChannelUnit:channelUnitMock]; - [self.sut channel:channelUnitMock didPrepareLog:mockLog withInternalId:@"fake-id"]; + [self.sut channel:channelUnitMock didPrepareLog:mockLog internalId:@"fake-id" flags:MSFlagsDefault]; } - (void)testDoesNotFilterValidCommonSchemaLogs { @@ -418,7 +452,7 @@ - (void)testFiltersNonOneCollectorLogWhenLogHasTargetTokens { [transmissionTargetTokens addObject:@"fake-transmission-target-token"]; MSCommonSchemaLog *commonSchemaLog = [MSCommonSchemaLog new]; id mockLog = OCMProtocolMock(@protocol(MSMockLogWithConversion)); - OCMStub([mockLog toCommonSchemaLogs]).andReturn(@[ commonSchemaLog ]); + OCMStub([mockLog toCommonSchemaLogsWithFlags:MSFlagsDefault]).andReturn(@[ commonSchemaLog ]); OCMStub(mockLog.transmissionTargetTokens).andReturn(transmissionTargetTokens); // When @@ -449,7 +483,7 @@ - (void)testValidateLog { // Valid data. log.name = @"valid.CS.event.name"; log.data = [MSCSData new]; - log.data.properties = @{ @"validkey" : @"validvalue" }; + log.data.properties = @{@"validkey" : @"validvalue"}; // Then XCTAssertTrue([self.sut validateLog:log]); diff --git a/AppCenter/AppCenterTests/MSStoragePerfomanceTests.m b/AppCenter/AppCenterTests/MSStoragePerformanceTests.m similarity index 87% rename from AppCenter/AppCenterTests/MSStoragePerfomanceTests.m rename to AppCenter/AppCenterTests/MSStoragePerformanceTests.m index e9dbf60faf..c248198224 100644 --- a/AppCenter/AppCenterTests/MSStoragePerfomanceTests.m +++ b/AppCenter/AppCenterTests/MSStoragePerformanceTests.m @@ -6,16 +6,16 @@ static const int kMSNumServices = 5; static NSString *const kMSTestGroupId = @"TestGroupId"; -@interface MSStoragePerfomanceTests : XCTestCase +@interface MSStoragePerformanceTests : XCTestCase @end -@interface MSStoragePerfomanceTests () +@interface MSStoragePerformanceTests () @property(nonatomic) MSLogDBStorage *dbStorage; @end -@implementation MSStoragePerfomanceTests +@implementation MSStoragePerformanceTests @synthesize dbStorage; @@ -35,7 +35,7 @@ - (void)testDatabaseWriteShortLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithShortServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } @@ -44,7 +44,7 @@ - (void)testDatabaseWriteLongLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } @@ -53,7 +53,7 @@ - (void)testDatabaseWriteVeryLongLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithVeryLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } @@ -64,7 +64,7 @@ - (void)testFileStorageWriteShortLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithShortServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } @@ -73,7 +73,7 @@ - (void)testFileStorageWriteLongLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } @@ -82,7 +82,7 @@ - (void)testFileStorageWriteVeryLongLogsPerformance { NSArray *arrayOfLogs = [self generateLogsWithVeryLongServicesNames:kMSNumLogs withNumService:kMSNumServices]; [self measureBlock:^{ for (MSStartServiceLog *log in arrayOfLogs) { - [self.dbStorage saveLog:log withGroupId:kMSTestGroupId]; + [self.dbStorage saveLog:log withGroupId:kMSTestGroupId flags:MSFlagsDefault]; } }]; } diff --git a/AppCenter/AppCenterTests/MSWrapperLoggerTests.m b/AppCenter/AppCenterTests/MSWrapperLoggerTests.m new file mode 100644 index 0000000000..941dc2d465 --- /dev/null +++ b/AppCenter/AppCenterTests/MSWrapperLoggerTests.m @@ -0,0 +1,38 @@ +#import "MSLoggerInternal.h" +#import "MSTestFrameworks.h" +#import "MSWrapperLogger.h" + +@interface MSWrapperLoggerTests : XCTestCase + +@end + +@implementation MSWrapperLoggerTests + +- (void)testWrapperLogger { + + // If + __block XCTestExpectation *expectation = [self expectationWithDescription:@"Wrapper logger"]; + __block NSString *expectedMessage = @"expectedMessage"; + NSString *tag = @"TAG"; + __block NSString *message = nil; + MSLogMessageProvider messageProvider = ^() { + message = expectedMessage; + [expectation fulfill]; + return message; + }; + + // When + [MSLogger setCurrentLogLevel:MSLogLevelDebug]; + [MSWrapperLogger MSWrapperLog:messageProvider tag:tag level:MSLogLevelDebug]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(NSError *_Nullable error) { + if (error) { + XCTFail(@"Expectation Failed with error: %@", error); + } + XCTAssertEqual(expectedMessage, message); + }]; +} + +@end diff --git a/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.h b/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.h new file mode 100644 index 0000000000..398b4a9247 --- /dev/null +++ b/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.h @@ -0,0 +1,49 @@ +#import + +@interface MSDelegateForwarderTestUtil : NSObject + +/** + * Generate a random class name. + * + * @return a class name. + */ ++ (NSString *)generateClassName; + +/** + * Create an instance of an object conforming to the given protocol. + * + * @param protocol Protocol to conform to. + * + * @return An instance of an object conforming to the given protocol. + */ ++ (id)createInstanceConformingToProtocol:(Protocol *)protocol; + +/** + * Create an instance of an object inheriting from the given base class and conforming to the given protocol. + * + * @param class Base class to inherit from. + * @param protocol Protocol to conform to. + * + * @return An instance of an object inheriting from the given base class and conforming to the given protocol. + */ ++ (id)createInstanceWithBaseClass:(Class)class andConformItToProtocol:(Protocol *)protocol; + +/** + * Add a selector with implementation to an instance. + * + * @param selector Selector. + * @param block Implementation. + * @param instance Instance to extend. + */ ++ (void)addSelector:(SEL)selector implementation:(id)block toInstance:(id)instance; + +/** + * Add a selector with implementation to a class. + * + * @param selector Selector. + * @param block Implementation. + * @param class Class to extend. + */ ++ (void)addSelector:(SEL)selector implementation:(id)block toClass:(id)class; + +@end diff --git a/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.m b/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.m new file mode 100644 index 0000000000..1b54f5fe86 --- /dev/null +++ b/AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.m @@ -0,0 +1,39 @@ +#import + +#import "MSDelegateForwarderTestUtil.h" +#import "MSUtility.h" + +@implementation MSDelegateForwarderTestUtil + ++ (NSString *)generateClassName { + return [@"C" stringByAppendingString:MS_UUID_STRING]; +} + ++ (id)createInstanceConformingToProtocol:(Protocol *)protocol { + return [self createInstanceWithBaseClass:[NSObject class] andConformItToProtocol:protocol]; +} + ++ (id)createInstanceWithBaseClass:(Class)class andConformItToProtocol:(Protocol *)protocol { + + // Generate class name to prevent conflicts in runtime added classes. + const char *name = [[self generateClassName] UTF8String]; + Class newClass = objc_allocateClassPair(class, name, 0); + if (protocol) { + class_addProtocol(newClass, protocol); + } + objc_registerClassPair(newClass); + return [newClass new]; +} + ++ (void)addSelector:(SEL)selector implementation:(id)block toInstance:(id)instance { + [self addSelector:selector implementation:block toClass:[instance class]]; +} + ++ (void)addSelector:(SEL)selector implementation:(id)block toClass:(id)class { + Method method = class_getInstanceMethod(class, selector); + const char *types = method_getTypeEncoding(method); + IMP imp = imp_implementationWithBlock(block); + class_addMethod(class, selector, imp, types); +} + +@end diff --git a/AppCenter/AppCenterTests/Util/MSStorageTestUtil.m b/AppCenter/AppCenterTests/Util/MSStorageTestUtil.m index ed9e47d902..abf0c32000 100644 --- a/AppCenter/AppCenterTests/Util/MSStorageTestUtil.m +++ b/AppCenter/AppCenterTests/Util/MSStorageTestUtil.m @@ -1,6 +1,5 @@ #import -#import "MSDBStoragePrivate.h" #import "MSStorageTestUtil.h" #import "MSUtility+File.h" @@ -24,10 +23,14 @@ - (long)getDataLengthInBytes { sqlite3_stmt *statement = NULL; sqlite3_prepare_v2(db, "PRAGMA page_count;", -1, &statement, NULL); sqlite3_step(statement); - NSNumber *pageCount = @(sqlite3_column_int(statement, 0)); + int pageCount = sqlite3_column_int(statement, 0); + sqlite3_finalize(statement); + sqlite3_prepare_v2(db, "PRAGMA page_size;", -1, &statement, NULL); + sqlite3_step(statement); + int pageSize = sqlite3_column_int(statement, 0); sqlite3_finalize(statement); sqlite3_close(db); - return [pageCount longValue] * kMSDefaultPageSizeInBytes; + return (long)pageCount * pageSize; } - (sqlite3 *)openDatabase { diff --git a/AppCenterAnalytics/AppCenterAnalytics.xcodeproj/project.pbxproj b/AppCenterAnalytics/AppCenterAnalytics.xcodeproj/project.pbxproj index 5ab8f87205..6c8497906c 100644 --- a/AppCenterAnalytics/AppCenterAnalytics.xcodeproj/project.pbxproj +++ b/AppCenterAnalytics/AppCenterAnalytics.xcodeproj/project.pbxproj @@ -85,6 +85,9 @@ 042B1D2A1FE3508100D6E04A /* MSSessionTrackerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 042B1D291FE3508100D6E04A /* MSSessionTrackerPrivate.h */; }; 042B1D2B1FE3516000D6E04A /* MSSessionTrackerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 042B1D291FE3508100D6E04A /* MSSessionTrackerPrivate.h */; }; 042B1D2C1FE3516200D6E04A /* MSSessionTrackerPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 042B1D291FE3508100D6E04A /* MSSessionTrackerPrivate.h */; }; + 042E5E012175230600AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 042E5E002175230600AFD6F9 /* MSPropertyConfiguratorInternal.h */; }; + 042E5E022175234400AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 042E5E002175230600AFD6F9 /* MSPropertyConfiguratorInternal.h */; }; + 042E5E032175234500AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 042E5E002175230600AFD6F9 /* MSPropertyConfiguratorInternal.h */; }; 043121511EE0C20A007054C5 /* MSAnalytics.m in Sources */ = {isa = PBXBuildFile; fileRef = E85547C51D2D6253002DF6E2 /* MSAnalytics.m */; }; 043121521EE0C20A007054C5 /* MSSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E48F9D1D515C3900A8C1B0 /* MSSessionTracker.m */; }; 043121531EE0C20A007054C5 /* MSPageLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 6E3E2CC81D359D5E00B1EE50 /* MSPageLog.m */; }; @@ -145,6 +148,9 @@ 04A082071F74BB8C00DC776D /* MSService.h in Headers */ = {isa = PBXBuildFile; fileRef = 04A082051F74BB8600DC776D /* MSService.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04A1409D1ECE728B001CEE94 /* MSAnalyticsCategory.h in Headers */ = {isa = PBXBuildFile; fileRef = 0485AF8D1EAA852A00C10CAF /* MSAnalyticsCategory.h */; }; 04A1409E1ECE7316001CEE94 /* MSAnalyticsCategory.m in Sources */ = {isa = PBXBuildFile; fileRef = 0485AF8E1EAA852A00C10CAF /* MSAnalyticsCategory.m */; }; + 04B525BD2194D49C00FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525BC2194D49C00FA37FD /* MSConstants+Flags.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 04B525BE2194D4CF00FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525BC2194D49C00FA37FD /* MSConstants+Flags.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 04B525BF2194D4D000FA37FD /* MSConstants+Flags.h in Headers */ = {isa = PBXBuildFile; fileRef = 04B525BC2194D49C00FA37FD /* MSConstants+Flags.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04BED7181ECB64FB00E20975 /* OCHamcrest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04BED7161ECB64CE00E20975 /* OCHamcrest.framework */; }; 266ED8969C431B7DADB1F57A /* MSEventPropertiesInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 266ED07D29C2A2CC67AD1F60 /* MSEventPropertiesInternal.h */; }; 266ED99A87F6251A106F7945 /* MSEventProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = 266ED289BE3D9562F1F61348 /* MSEventProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -172,6 +178,9 @@ 35A204BF216C1CCB00FEBADA /* MSEventPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35A204BE216C1AC600FEBADA /* MSEventPropertiesTests.m */; }; 35A204C0216C1CCE00FEBADA /* MSEventPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35A204BE216C1AC600FEBADA /* MSEventPropertiesTests.m */; }; 35A204C1216C1CCF00FEBADA /* MSEventPropertiesTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 35A204BE216C1AC600FEBADA /* MSEventPropertiesTests.m */; }; + 35B14E272177EEBD00529353 /* MSAnalyticsConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA03D5D8EA1FEA1DC687CEF /* MSAnalyticsConstants.h */; }; + 35B14E282177EEBF00529353 /* MSAnalyticsConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA03D5D8EA1FEA1DC687CEF /* MSAnalyticsConstants.h */; }; + 35B14E292177EEC000529353 /* MSAnalyticsConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 2DA03D5D8EA1FEA1DC687CEF /* MSAnalyticsConstants.h */; }; 35BF19E51DF9D59E00193027 /* MSMockAnalyticsDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 35BF19E41DF9D59E00193027 /* MSMockAnalyticsDelegate.m */; }; 3813B9651DBFE68200831214 /* MSAnalyticsInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 3813B9641DBFE68200831214 /* MSAnalyticsInternal.h */; }; 38337D6F20C0AB0D00CEDA17 /* MSEventLogPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38337D6E20C0AB0D00CEDA17 /* MSEventLogPrivate.h */; }; @@ -243,6 +252,9 @@ E8E48F9F1D515C3900A8C1B0 /* MSSessionTracker.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E48F9D1D515C3900A8C1B0 /* MSSessionTracker.m */; }; E8E48FA21D51670100A8C1B0 /* MSStartSessionLog.h in Headers */ = {isa = PBXBuildFile; fileRef = E8E48FA01D51670100A8C1B0 /* MSStartSessionLog.h */; }; E8E48FA31D51670100A8C1B0 /* MSStartSessionLog.m in Sources */ = {isa = PBXBuildFile; fileRef = E8E48FA11D51670100A8C1B0 /* MSStartSessionLog.m */; }; + F82E4C71217F1FA600EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C70217F1FA600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C72217F1FA600EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C70217F1FA600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C73217F1FA600EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C70217F1FA600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; F834E37320AC8E25003CB54D /* MSLogWithNameAndProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F834E37020AC8E25003CB54D /* MSLogWithNameAndProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; F834E37420AC8E25003CB54D /* MSLogWithNameAndProperties.h in Headers */ = {isa = PBXBuildFile; fileRef = F834E37020AC8E25003CB54D /* MSLogWithNameAndProperties.h */; settings = {ATTRIBUTES = (Public, ); }; }; F834E37520AC8E25003CB54D /* MSLogWithNameAndProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = F834E37120AC8E25003CB54D /* MSLogWithNameAndProperties.m */; }; @@ -332,6 +344,7 @@ 0420A6921ECA7B1100915619 /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = ../../iOS/OCMock/OCMock.framework; sourceTree = ""; }; 042A17A71DEFA950003BA80A /* MSAnalyticsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAnalyticsTests.m; sourceTree = ""; }; 042B1D291FE3508100D6E04A /* MSSessionTrackerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSSessionTrackerPrivate.h; sourceTree = ""; }; + 042E5E002175230600AFD6F9 /* MSPropertyConfiguratorInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPropertyConfiguratorInternal.h; path = ../TransmissionTarget/MSPropertyConfiguratorInternal.h; sourceTree = ""; }; 04311FF11EE0858F007054C5 /* MSTestFrameworks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSTestFrameworks.h; path = ../../../AppCenter/AppCenterTests/Util/MSTestFrameworks.h; sourceTree = ""; }; 0431216C1EE0C20A007054C5 /* libAppCenterAnalytics.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAppCenterAnalytics.a; sourceTree = BUILT_PRODUCTS_DIR; }; 043121721EE0C248007054C5 /* tvOS.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = tvOS.modulemap; sourceTree = ""; }; @@ -352,12 +365,14 @@ 049BC82B1ECE3B9A00FB6719 /* macOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = macOS.xcconfig; sourceTree = ""; }; 049BC82C1ECE3CF000FB6719 /* Tests macOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = "Tests macOS.xcconfig"; path = "../../Config/Tests macOS.xcconfig"; sourceTree = ""; }; 04A082051F74BB8600DC776D /* MSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSService.h; path = ../AppCenter/AppCenter/MSService.h; sourceTree = ""; }; + 04B525BC2194D49C00FA37FD /* MSConstants+Flags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "MSConstants+Flags.h"; path = "../AppCenter/AppCenter/MSConstants+Flags.h"; sourceTree = ""; }; 04BED7161ECB64CE00E20975 /* OCHamcrest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCHamcrest.framework; path = ../../Vendor/macOS/OCHamcrest/OCHamcrest.framework; sourceTree = ""; }; 04ED31E91EAAD32B0033BAAE /* macOS.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = macOS.modulemap; sourceTree = ""; }; 04ED31EB1EAAD3390033BAAE /* iOS.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iOS.modulemap; sourceTree = ""; }; 266ED07D29C2A2CC67AD1F60 /* MSEventPropertiesInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSEventPropertiesInternal.h; sourceTree = ""; }; 266ED289BE3D9562F1F61348 /* MSEventProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSEventProperties.h; sourceTree = ""; }; 266EDBD8487DA42DAB601DD9 /* MSEventProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSEventProperties.m; sourceTree = ""; }; + 2DA03D5D8EA1FEA1DC687CEF /* MSAnalyticsConstants.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSAnalyticsConstants.h; sourceTree = ""; }; 3513432F205704A100E6DC7D /* MSAnalyticsTransmissionTarget.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSAnalyticsTransmissionTarget.h; sourceTree = ""; }; 35134330205704C100E6DC7D /* MSAnalyticsTransmissionTarget.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSAnalyticsTransmissionTarget.m; sourceTree = ""; }; 351343352057093600E6DC7D /* MSAnalyticsTransmissionTargetInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSAnalyticsTransmissionTargetInternal.h; sourceTree = ""; }; @@ -410,6 +425,7 @@ E8E48F9D1D515C3900A8C1B0 /* MSSessionTracker.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = MSSessionTracker.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; E8E48FA01D51670100A8C1B0 /* MSStartSessionLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSStartSessionLog.h; sourceTree = ""; }; E8E48FA11D51670100A8C1B0 /* MSStartSessionLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSStartSessionLog.m; sourceTree = ""; }; + F82E4C70217F1FA600EDAB34 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; F834E37020AC8E25003CB54D /* MSLogWithNameAndProperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSLogWithNameAndProperties.h; sourceTree = ""; }; F834E37120AC8E25003CB54D /* MSLogWithNameAndProperties.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSLogWithNameAndProperties.m; sourceTree = ""; }; /* End PBXFileReference section */ @@ -509,6 +525,7 @@ children = ( 0485AF8D1EAA852A00C10CAF /* MSAnalyticsCategory.h */, 0485AF8E1EAA852A00C10CAF /* MSAnalyticsCategory.m */, + 2DA03D5D8EA1FEA1DC687CEF /* MSAnalyticsConstants.h */, ); path = Util; sourceTree = ""; @@ -588,6 +605,7 @@ 6E0684361D35A39300A8CC6C /* Frameworks */ = { isa = PBXGroup; children = ( + F82E4C70217F1FA600EDAB34 /* sqlite3.c */, 0446DF631F3B95F200C8E338 /* libAppCenter.a */, 0446DF611F3B95EB00C8E338 /* libAppCenter.a */, 0446DF5E1F3B8FBA00C8E338 /* libAppCenter.a */, @@ -629,6 +647,7 @@ isa = PBXGroup; children = ( 358F9BC72019596400B9E22C /* MSAbstractLog.h */, + 04B525BC2194D49C00FA37FD /* MSConstants+Flags.h */, 358F9BC52019590100B9E22C /* MSLogWithProperties.h */, 04A082051F74BB8600DC776D /* MSService.h */, 387C77041D6CC39400D68CC1 /* MSServiceAbstract.h */, @@ -677,6 +696,7 @@ 351343352057093600E6DC7D /* MSAnalyticsTransmissionTargetInternal.h */, C27452D520AE0EF100B64B68 /* MSAnalytics+Validation.h */, C27452D120AE0DAC00B64B68 /* MSAnalytics+Validation.m */, + 042E5E002175230600AFD6F9 /* MSPropertyConfiguratorInternal.h */, E758FA8320FFE0E200011793 /* MSPropertyConfiguratorPrivate.h */, 6E3E2CC21D359D5E00B1EE50 /* Model */, E8E48F9B1D515C1F00A8C1B0 /* Session */, @@ -743,6 +763,7 @@ 0431215B1EE0C20A007054C5 /* AppCenter+Internal.h in Headers */, 0431215C1EE0C20A007054C5 /* MSPageLog.h in Headers */, E758FA8620FFE0E200011793 /* MSPropertyConfiguratorPrivate.h in Headers */, + 042E5E032175234500AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */, F834E37420AC8E25003CB54D /* MSLogWithNameAndProperties.h in Headers */, B2ADB1332123A72000D0D7D9 /* MSAnalyticsAuthenticationProviderDelegate.h in Headers */, 38337D7120C0AB0D00CEDA17 /* MSEventLogPrivate.h in Headers */, @@ -764,9 +785,11 @@ C27452D820AE0EF100B64B68 /* MSAnalytics+Validation.h in Headers */, 043121651EE0C20A007054C5 /* MSAnalytics.h in Headers */, 04A082071F74BB8C00DC776D /* MSService.h in Headers */, + 35B14E292177EEC000529353 /* MSAnalyticsConstants.h in Headers */, 043121681EE0C20A007054C5 /* MSSessionTracker.h in Headers */, 99B8D3C9216BCED70021C47D /* MSEventPropertiesInternal.h in Headers */, 99B8D3C7216BCECA0021C47D /* MSEventProperties.h in Headers */, + 04B525BF2194D4D000FA37FD /* MSConstants+Flags.h in Headers */, 042B1D2C1FE3516200D6E04A /* MSSessionTrackerPrivate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -779,6 +802,7 @@ 04A1409D1ECE728B001CEE94 /* MSAnalyticsCategory.h in Headers */, F834E37320AC8E25003CB54D /* MSLogWithNameAndProperties.h in Headers */, E758FA8520FFE0E200011793 /* MSPropertyConfiguratorPrivate.h in Headers */, + 042E5E022175234400AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */, 0485AF741EAA79E300C10CAF /* AppCenter+Internal.h in Headers */, B2ADB1322123A72000D0D7D9 /* MSAnalyticsAuthenticationProviderDelegate.h in Headers */, 38337D7020C0AB0D00CEDA17 /* MSEventLogPrivate.h in Headers */, @@ -800,9 +824,11 @@ C27452D720AE0EF100B64B68 /* MSAnalytics+Validation.h in Headers */, 0485AF7C1EAA79E300C10CAF /* MSAnalyticsPrivate.h in Headers */, 0485AF7E1EAA79E300C10CAF /* MSAnalytics.h in Headers */, + 35B14E282177EEBF00529353 /* MSAnalyticsConstants.h in Headers */, 0485AF811EAA79E300C10CAF /* MSSessionTracker.h in Headers */, 99B8D3C8216BCED60021C47D /* MSEventPropertiesInternal.h in Headers */, 99B8D3C6216BCEC90021C47D /* MSEventProperties.h in Headers */, + 04B525BE2194D4CF00FA37FD /* MSConstants+Flags.h in Headers */, 042B1D2B1FE3516000D6E04A /* MSSessionTrackerPrivate.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -815,6 +841,7 @@ E89B33EC1D5A8F3500FDE8FB /* MSSessionTrackerDelegate.h in Headers */, 04A082061F74BB8600DC776D /* MSService.h in Headers */, E758FA8420FFE0E200011793 /* MSPropertyConfiguratorPrivate.h in Headers */, + 042E5E012175230600AFD6F9 /* MSPropertyConfiguratorInternal.h in Headers */, F8A10EBD20AD89010082014E /* MSLogWithNameAndProperties.h in Headers */, B2ADB1312123A72000D0D7D9 /* MSAnalyticsAuthenticationProviderDelegate.h in Headers */, 38337D6F20C0AB0D00CEDA17 /* MSEventLogPrivate.h in Headers */, @@ -836,9 +863,11 @@ C27452D620AE0EF100B64B68 /* MSAnalytics+Validation.h in Headers */, E85547FA1D2D6A9E002DF6E2 /* MSAnalyticsPrivate.h in Headers */, E85547F81D2D6A7D002DF6E2 /* MSAnalytics.h in Headers */, + 35B14E272177EEBD00529353 /* MSAnalyticsConstants.h in Headers */, E8E48F9E1D515C3900A8C1B0 /* MSSessionTracker.h in Headers */, 042B1D2A1FE3508100D6E04A /* MSSessionTrackerPrivate.h in Headers */, 266ED99A87F6251A106F7945 /* MSEventProperties.h in Headers */, + 04B525BD2194D49C00FA37FD /* MSConstants+Flags.h in Headers */, 266ED8969C431B7DADB1F57A /* MSEventPropertiesInternal.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1182,6 +1211,7 @@ 0446DF441F3B8E6300C8E338 /* MSEventLogTests.m in Sources */, 35A204C1216C1CCF00FEBADA /* MSEventPropertiesTests.m in Sources */, B26D4E19212240F400AB4E28 /* MSAnalyticsAuthenticationProviderTests.m in Sources */, + F82E4C73217F1FA600EDAB34 /* sqlite3.c in Sources */, 0446DF451F3B8E6300C8E338 /* MSPageLogTests.m in Sources */, 38C215E320E692C700191F3C /* MSMockUserDefaults.m in Sources */, 0446DF461F3B8E6300C8E338 /* MSSessionTrackerTests.m in Sources */, @@ -1202,6 +1232,7 @@ 046AEAB91ECA51AF00CBE511 /* MSEventLogTests.m in Sources */, B26D4E18212240EB00AB4E28 /* MSAnalyticsAuthenticationProviderTests.m in Sources */, 046AEABA1ECA51AF00CBE511 /* MSPageLogTests.m in Sources */, + F82E4C72217F1FA600EDAB34 /* sqlite3.c in Sources */, 38C215E220E692C700191F3C /* MSMockUserDefaults.m in Sources */, 046AEABB1ECA51AF00CBE511 /* MSSessionTrackerTests.m in Sources */, 046AEABC1ECA51AF00CBE511 /* MSStartSessionLogTests.m in Sources */, @@ -1259,6 +1290,7 @@ 6E3E2CD31D359F3300B1EE50 /* MSEventLogTests.m in Sources */, 6E3E2CD41D359F3300B1EE50 /* MSPageLogTests.m in Sources */, B26D4DDF211BA99E00AB4E28 /* MSAnalyticsAuthenticationProviderTests.m in Sources */, + F82E4C71217F1FA600EDAB34 /* sqlite3.c in Sources */, 38C215E120E692C700191F3C /* MSMockUserDefaults.m in Sources */, E815912C1D526A09003D5F3C /* MSSessionTrackerTests.m in Sources */, E815912A1D526956003D5F3C /* MSStartSessionLogTests.m in Sources */, diff --git a/AppCenterAnalytics/AppCenterAnalytics/AppCenterAnalytics.h b/AppCenterAnalytics/AppCenterAnalytics/AppCenterAnalytics.h index d7c1fec9dd..a46df38952 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/AppCenterAnalytics.h +++ b/AppCenterAnalytics/AppCenterAnalytics/AppCenterAnalytics.h @@ -5,5 +5,6 @@ #import "MSAnalyticsTransmissionTarget.h" #import "MSAnalyticsAuthenticationProvider.h" #import "MSAnalyticsAuthenticationProviderDelegate.h" +#import "MSConstants+Flags.h" #import "MSEventLog.h" #import "MSEventProperties.h" diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsInternal.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsInternal.h index ca882e61e0..beec458471 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsInternal.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsInternal.h @@ -9,15 +9,19 @@ NS_ASSUME_NONNULL_BEGIN @interface MSAnalytics () /** - * Track an event. + * Track an event with typed properties. * * @param eventName Event name. - * @param properties Dictionary of properties. + * @param properties The typed event properties. * @param transmissionTarget The transmission target to associate to this event. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. */ + (void)trackEvent:(NSString *)eventName - withProperties:(nullable NSDictionary *)properties - forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget; + withTypedProperties:(nullable MSEventProperties *)properties + forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags; // Temporarily hiding tracking page feature. /** @@ -49,6 +53,11 @@ NS_ASSUME_NONNULL_BEGIN */ + (BOOL)isAutoPageTrackingEnabled; +/** + * Set the MSAnalyticsDelegate object. + * + * @param delegate The delegate to be set. + */ + (void)setDelegate:(nullable id)delegate; /** diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsPrivate.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsPrivate.h index 15f4511ac5..49a5af8f7b 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsPrivate.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSAnalyticsPrivate.h @@ -39,10 +39,14 @@ NS_ASSUME_NONNULL_BEGIN * @param eventName Event name. * @param properties Dictionary of properties. * @param transmissionTarget Transmission target to associate with the event. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. */ - (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties - forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget; + forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags; /** * Track an event with typed properties. @@ -50,10 +54,14 @@ NS_ASSUME_NONNULL_BEGIN * @param eventName Event name. * @param properties Typed properties. * @param transmissionTarget Transmission target to associate with the event. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. */ - (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties - forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget; + forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags; /** * Track a page. diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSPropertyConfiguratorPrivate.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSPropertyConfiguratorPrivate.h index 0f30fe4997..a4d147fe27 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/MSPropertyConfiguratorPrivate.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/MSPropertyConfiguratorPrivate.h @@ -1,11 +1,12 @@ #import #import "MSAnalyticsTransmissionTarget.h" -#import "MSChannelDelegate.h" + +@class MSTypedProperty; NS_ASSUME_NONNULL_BEGIN -@interface MSPropertyConfigurator () +@interface MSPropertyConfigurator () /** * The application version to be overwritten. @@ -30,18 +31,13 @@ NS_ASSUME_NONNULL_BEGIN /** * Event properties attached to events tracked by this target. */ -@property(nonatomic, nullable) NSMutableDictionary *eventProperties; +@property(nonatomic) MSEventProperties *eventProperties; /** * The device id to send with common schema logs. If nil, nothing is sent. */ @property(nonatomic, copy) NSString *deviceId; -/** - * Initialize property configurator with a transmission target. - */ -- (instancetype)initWithTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget; - @end NS_ASSUME_NONNULL_END diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.h index 57750eb49e..6e41339500 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.h @@ -1,6 +1,7 @@ #import "MSLogWithNameAndProperties.h" @class MSEventProperties; +@class MSMetadataExtension; @interface MSEventLog : MSLogWithNameAndProperties diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.m b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.m index 6838ccb4ee..84cfa8d102 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.m +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLog.m @@ -1,9 +1,19 @@ #import "AppCenter+Internal.h" +#import "MSACModelConstants.h" +#import "MSAnalyticsConstants.h" #import "MSAnalyticsInternal.h" +#import "MSBooleanTypedProperty.h" #import "MSCSData.h" +#import "MSCSExtensions.h" #import "MSCSModelConstants.h" +#import "MSConstants+Internal.h" +#import "MSDateTimeTypedProperty.h" +#import "MSDoubleTypedProperty.h" #import "MSEventLogPrivate.h" #import "MSEventPropertiesInternal.h" +#import "MSLongTypedProperty.h" +#import "MSMetadataExtension.h" +#import "MSStringTypedProperty.h" static NSString *const kMSTypeEvent = @"event"; @@ -16,6 +26,11 @@ @implementation MSEventLog - (instancetype)init { if ((self = [super init])) { self.type = kMSTypeEvent; + _metadataTypeIdMapping = @{ + kMSLongTypedPropertyType : @(kMSLongMetadataTypeId), + kMSDoubleTypedPropertyType : @(kMSDoubleMetadataTypeId), + kMSDateTimeTypedPropertyType : @(kMSDateTimeMetadataTypeId) + }; } return self; } @@ -62,59 +77,118 @@ - (void)encodeWithCoder:(NSCoder *)coder { #pragma mark - MSAbstractLog -- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token { - MSCommonSchemaLog *csLog = [super toCommonSchemaLogForTargetToken:token]; +- (MSCommonSchemaLog *)toCommonSchemaLogForTargetToken:(NSString *)token flags:(MSFlags)flags { + MSCommonSchemaLog *csLog = [super toCommonSchemaLogForTargetToken:token flags:flags]; // Event name goes to part A. csLog.name = self.name; + // Metadata extension must accompany data. // Event properties goes to part C. - MSCSData *data = [MSCSData new]; - csLog.data = data; - csLog.data.properties = [self convertACPropertiesToCSproperties:self.properties]; + [self setPropertiesAndMetadataForCSLog:csLog]; + csLog.tag = self.tag; return csLog; } #pragma mark - Helper -- (NSDictionary *)convertACPropertiesToCSproperties:(NSDictionary *)acProperties { +- (void)setPropertiesAndMetadataForCSLog:(MSCommonSchemaLog *)csLog { NSMutableDictionary *csProperties; - if (acProperties) { + NSMutableDictionary *metadata; + if (self.typedProperties) { csProperties = [NSMutableDictionary new]; - for (NSString *acKey in acProperties) { + metadata = [NSMutableDictionary new]; + for (MSTypedProperty *typedProperty in [self.typedProperties.properties objectEnumerator]) { // Properties keys are mixed up with other keys from Data, make sure they don't conflict. - if ([acKey isEqualToString:kMSDataBaseData] || [acKey isEqualToString:kMSDataBaseDataType]) { - MSLogWarning(MSAnalytics.logTag, @"Cannot use %@ in properties, skipping that property.", acKey); + if ([typedProperty.name isEqualToString:kMSDataBaseData] || [typedProperty.name isEqualToString:kMSDataBaseDataType]) { + MSLogWarning(MSAnalytics.logTag, @"Cannot use %@ in properties, skipping that property.", typedProperty.name); continue; } + [self addTypedProperty:typedProperty toCSMetadata:metadata andCSProperties:csProperties]; + } + } + if (csProperties.count != 0) { + csLog.data = [MSCSData new]; + csLog.data.properties = csProperties; + } + if (metadata.count != 0) { + csLog.ext.metadataExt = [MSMetadataExtension new]; + csLog.ext.metadataExt.metadata = metadata; + } +} - // If the key contains a '.' then it's nested objects (i.e: "a.b":"value" => {"a":{"b":"value"}}). - NSArray *csKeys = [acKey componentsSeparatedByString:@"."]; - NSUInteger lastIndex = csKeys.count - 1; - NSMutableDictionary *destProperties = csProperties; - for (NSUInteger i = 0; i < lastIndex; i++) { - NSMutableDictionary *subObject = nil; - if ([(NSObject *) destProperties[csKeys[i]] isKindOfClass:[NSMutableDictionary class]]) { - subObject = destProperties[csKeys[i]]; - } - if (!subObject) { - if (destProperties[csKeys[i]]) { - MSLogWarning(MSAnalytics.logTag, @"Property key '%@' already has a value, the old value will be overridden.", csKeys[i]); - } - subObject = [NSMutableDictionary new]; - destProperties[csKeys[i]] = subObject; - } - destProperties = subObject; +- (void)addTypedProperty:(MSTypedProperty *)typedProperty + toCSMetadata:(NSMutableDictionary *)csMetadata + andCSProperties:(NSMutableDictionary *)csProperties { + NSNumber *typeId = self.metadataTypeIdMapping[typedProperty.type]; + + // If the key contains a '.' then it's nested objects (i.e: "a.b":"value" => {"a":{"b":"value"}}). + NSArray *csKeys = [typedProperty.name componentsSeparatedByString:@"."]; + NSMutableDictionary *propertyTree = csProperties; + NSMutableDictionary *metadataTree = csMetadata; + + /* + * Keep track of the subtree that contains all the metadata levels added in the for loop. + * Thus if it needs to be removed, a second traversal is not needed. + * The metadata should be cleaned up if the property is not added due to a key collision. + */ + NSMutableDictionary *metadataSubtreeParent = nil; + for (NSUInteger i = 0; i < csKeys.count - 1; i++) { + id key = csKeys[i]; + if (![(NSObject *)propertyTree[key] isKindOfClass:[NSMutableDictionary class]]) { + if (propertyTree[key]) { + propertyTree = nil; + break; + } + propertyTree[key] = [NSMutableDictionary new]; + } + propertyTree = propertyTree[key]; + if (typeId) { + if (!metadataTree[kMSFieldDelimiter]) { + metadataTree[kMSFieldDelimiter] = [NSMutableDictionary new]; + metadataSubtreeParent = metadataSubtreeParent ?: metadataTree; } - if (destProperties[csKeys[lastIndex]]) { - [destProperties removeObjectForKey:csKeys[lastIndex]]; - MSLogWarning(MSAnalytics.logTag, @"Property key '%@' already has a value, the old value will be overridden.", csKeys[lastIndex]); + if (!metadataTree[kMSFieldDelimiter][key]) { + metadataTree[kMSFieldDelimiter][key] = [NSMutableDictionary new]; } - destProperties[csKeys[lastIndex]] = acProperties[acKey]; + metadataTree = metadataTree[kMSFieldDelimiter][key]; } } - return csProperties; + id lastKey = csKeys.lastObject; + BOOL didAddTypedProperty = [self addTypedProperty:typedProperty toPropertyTree:propertyTree withKey:lastKey]; + if (typeId && didAddTypedProperty) { + if (!metadataTree[kMSFieldDelimiter]) { + metadataTree[kMSFieldDelimiter] = [NSMutableDictionary new]; + } + metadataTree[kMSFieldDelimiter][lastKey] = typeId; + } else if (metadataSubtreeParent) { + [metadataSubtreeParent removeObjectForKey:kMSFieldDelimiter]; + } +} + +- (BOOL)addTypedProperty:(MSTypedProperty *)typedProperty toPropertyTree:(NSMutableDictionary *)propertyTree withKey:(NSString *)key { + if (!propertyTree || propertyTree[key]) { + MSLogWarning(MSAnalytics.logTag, @"Property key '%@' already has a value, choosing one.", key); + return NO; + } + if ([typedProperty isKindOfClass:[MSStringTypedProperty class]]) { + MSStringTypedProperty *stringProperty = (MSStringTypedProperty *)typedProperty; + propertyTree[key] = stringProperty.value; + } else if ([typedProperty isKindOfClass:[MSBooleanTypedProperty class]]) { + MSBooleanTypedProperty *boolProperty = (MSBooleanTypedProperty *)typedProperty; + propertyTree[key] = @(boolProperty.value); + } else if ([typedProperty isKindOfClass:[MSLongTypedProperty class]]) { + MSLongTypedProperty *longProperty = (MSLongTypedProperty *)typedProperty; + propertyTree[key] = @(longProperty.value); + } else if ([typedProperty isKindOfClass:[MSDoubleTypedProperty class]]) { + MSDoubleTypedProperty *doubleProperty = (MSDoubleTypedProperty *)typedProperty; + propertyTree[key] = @(doubleProperty.value); + } else if ([typedProperty isKindOfClass:[MSDateTimeTypedProperty class]]) { + MSDateTimeTypedProperty *dateProperty = (MSDateTimeTypedProperty *)typedProperty; + propertyTree[key] = [MSUtility dateToISO8601:dateProperty.value]; + } + return YES; } @end diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLogPrivate.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLogPrivate.h index 6838bcc76c..ec9cc93931 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLogPrivate.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventLogPrivate.h @@ -2,13 +2,14 @@ @interface MSEventLog () +/** + * Maps each typed property string identifier to a CS type identifier. + */ +@property(nonatomic) NSDictionary *metadataTypeIdMapping; + /** * Convert AppCenter properties to Common Schema 3.0 Part C properties. - * - * @param acProperties The AppCenter properties. - * - * @return A dictionary of key-value pairs. */ -- (NSDictionary *)convertACPropertiesToCSproperties:(NSDictionary *)acProperties; +- (void)setPropertiesAndMetadataForCSLog:(MSCommonSchemaLog *)csLog; @end diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventPropertiesInternal.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventPropertiesInternal.h index bce6f56034..ae771f3a93 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventPropertiesInternal.h +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/Model/MSEventPropertiesInternal.h @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN /** * String and date properties. */ -@property (nonatomic) NSMutableDictionary *properties; +@property(nonatomic) NSMutableDictionary *properties; /** * Creates an instance of EventProperties with a string-string properties dictionary. @@ -39,6 +39,13 @@ NS_ASSUME_NONNULL_BEGIN */ - (BOOL)isEmpty; +/** + * Merge event properties. + * + * @param eventProperties The new properites to be merged. + */ +- (void)mergeEventProperties:(MSEventProperties *__nonnull)eventProperties; + @end NS_ASSUME_NONNULL_END diff --git a/AppCenterAnalytics/AppCenterAnalytics/Internals/Util/MSAnalyticsConstants.h b/AppCenterAnalytics/AppCenterAnalytics/Internals/Util/MSAnalyticsConstants.h new file mode 100644 index 0000000000..05bfe19a2f --- /dev/null +++ b/AppCenterAnalytics/AppCenterAnalytics/Internals/Util/MSAnalyticsConstants.h @@ -0,0 +1,6 @@ +/** + * Common schema metadata type identifiers. + */ +static const int kMSLongMetadataTypeId = 4; +static const int kMSDoubleMetadataTypeId = 6; +static const int kMSDateTimeMetadataTypeId = 9; diff --git a/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.h b/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.h index aac5e1f367..8ac03b5728 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.h +++ b/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.h @@ -52,11 +52,85 @@ NS_ASSUME_NONNULL_BEGIN */ + (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties; +/** + * Track a custom event with optional string properties. + * + * @param eventName Event name. Cannot be `nil` or empty. + * @param properties Dictionary of properties. Keys and values must not be `nil`. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. + * + * @discussion Additional validation rules apply depending on the configured secret. + * + * For App Center: + * + * - The event name cannot be longer than 256 and is truncated otherwise. + * + * - The property names cannot be empty. + * + * - The property names and values are limited to 125 characters each (truncated). + * + * - The number of properties per event is limited to 20 (truncated). + * + * + * For One Collector: + * + * - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression. + * + * - The `baseData` and `baseDataType` properties are reserved and thus discarded. + * + * - The full event size when encoded as a JSON string cannot be larger than 1.9MB. + */ ++ (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties flags:(MSFlags)flags; + +/** + * Track a custom event with name and optional typed properties. + * + * @param eventName Event name. + * @param properties Typed properties. + * + * @discussion The following validation rules are applied: + * + * The name cannot be null or empty. + * + * The property names or values cannot be null. + * + * Double values must be finite (NaN or Infinite values are discarded). + * + * Additional validation rules apply depending on the configured secret. + * + * + * For App Center: + * + * - The event name cannot be longer than 256 and is truncated otherwise. + * + * - The property names cannot be empty. + * + * - The property names and values are limited to 125 characters each (truncated). + * + * - The number of properties per event is limited to 20 (truncated). + * + * + * For One Collector: + * + * - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression. + * + * - The `baseData` and `baseDataType` properties are reserved and thus discarded. + * + * - The full event size when encoded as a JSON string cannot be larger than 1.9MB. + */ ++ (void)trackEvent:(NSString *)eventName + withTypedProperties:(nullable MSEventProperties *)properties NS_SWIFT_NAME(trackEvent(_:withProperties:)); + /** * Track a custom event with name and optional typed properties. * * @param eventName Event name. * @param properties Typed properties. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. * * @discussion The following validation rules are applied: * @@ -88,7 +162,9 @@ NS_ASSUME_NONNULL_BEGIN * * - The full event size when encoded as a JSON string cannot be larger than 1.9MB. */ -+ (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties; ++ (void)trackEvent:(NSString *)eventName + withTypedProperties:(nullable MSEventProperties *)properties + flags:(MSFlags)flags NS_SWIFT_NAME(trackEvent(_:withProperties:flags:)); /** * Pause transmission of Analytics logs. While paused, Analytics logs are saved to disk. diff --git a/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.m b/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.m index a1400b9d32..98447b60ea 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.m +++ b/AppCenterAnalytics/AppCenterAnalytics/MSAnalytics.m @@ -81,7 +81,12 @@ - (void)startWithChannelGroup:(id)channelGroup fromApplication:(BOOL)fromApplication { [super startWithChannelGroup:channelGroup appSecret:appSecret transmissionTargetToken:token fromApplication:fromApplication]; if (token) { - self.defaultTransmissionTarget = [self transmissionTargetForToken:(NSString *)token]; + + /* + * Don't use [self transmissionTargetForToken] because that will add the default transmission target to the cache, but it should be + * separate. + */ + self.defaultTransmissionTarget = [self createTransmissionTargetForToken:token]; } // Set up swizzling for auto page tracking. @@ -153,7 +158,12 @@ - (void)updateConfigurationWithAppSecret:(NSString *)appSecret transmissionTarge // Create the default target if not already created in start. if (token && !self.defaultTransmissionTarget) { - self.defaultTransmissionTarget = [self transmissionTargetForToken:token]; + + /* + * Don't use [self transmissionTargetForToken] because that will add the default transmission target to the cache, but it should be + * separate. + */ + self.defaultTransmissionTarget = [self createTransmissionTargetForToken:token]; } } @@ -164,29 +174,42 @@ + (void)trackEvent:(NSString *)eventName { } + (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties { - [self trackEvent:eventName withProperties:properties forTransmissionTarget:nil]; + [self trackEvent:eventName withProperties:properties flags:MSFlagsDefault]; +} + ++ (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties flags:(MSFlags)flags { + [self trackEvent:eventName withProperties:properties forTransmissionTarget:nil flags:flags]; } + (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties { - [self trackEvent:eventName withTypedProperties:properties forTransmissionTarget:nil]; + [self trackEvent:eventName withTypedProperties:properties flags:MSFlagsDefault]; +} + ++ (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties flags:(MSFlags)flags { + [self trackEvent:eventName withTypedProperties:properties forTransmissionTarget:nil flags:flags]; } + (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties - forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget { + forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags { @synchronized(self) { if ([[MSAnalytics sharedInstance] canBeUsed]) { - [[MSAnalytics sharedInstance] trackEvent:eventName withProperties:properties forTransmissionTarget:transmissionTarget]; + [[MSAnalytics sharedInstance] trackEvent:eventName withProperties:properties forTransmissionTarget:transmissionTarget flags:flags]; } } } + (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties - forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget { + forTransmissionTarget:(nullable MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags { @synchronized(self) { if ([[MSAnalytics sharedInstance] canBeUsed]) { - [[MSAnalytics sharedInstance] trackEvent:eventName withTypedProperties:properties forTransmissionTarget:transmissionTarget]; + [[MSAnalytics sharedInstance] trackEvent:eventName + withTypedProperties:properties + forTransmissionTarget:transmissionTarget + flags:flags]; } } } @@ -249,15 +272,17 @@ + (void)resumeTransmissionTargetForToken:(NSString *)token { - (void)trackEvent:(NSString *)eventName withProperties:(NSDictionary *)properties - forTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget { + forTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags { NSDictionary *validProperties = [self removeInvalidProperties:properties]; MSEventProperties *eventProperties = [[MSEventProperties alloc] initWithStringDictionary:validProperties]; - [self trackEvent:eventName withTypedProperties:eventProperties forTransmissionTarget:transmissionTarget]; + [self trackEvent:eventName withTypedProperties:eventProperties forTransmissionTarget:transmissionTarget flags:flags]; } - (void)trackEvent:(NSString *)eventName withTypedProperties:(MSEventProperties *)properties - forTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget { + forTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget + flags:(MSFlags)flags { if (![self isEnabled]) { return; } @@ -267,6 +292,13 @@ - (void)trackEvent:(NSString *)eventName transmissionTarget = self.defaultTransmissionTarget; } + // Validate flags. + MSFlags persistenceFlag = flags & kMSPersistenceFlagsMask; + if (persistenceFlag != MSFlagsPersistenceNormal && persistenceFlag != MSFlagsPersistenceCritical) { + MSLogWarning([MSAnalytics logTag], @"Invalid flags (%u) received, using normal as a default.", (unsigned int)persistenceFlag); + persistenceFlag = MSFlagsPersistenceNormal; + } + // Create an event log. MSEventLog *log = [MSEventLog new]; @@ -274,6 +306,7 @@ - (void)trackEvent:(NSString *)eventName if (transmissionTarget) { if (transmissionTarget.isEnabled) { [log addTransmissionTargetToken:[transmissionTarget transmissionTargetToken]]; + log.tag = transmissionTarget; } else { MSLogError([MSAnalytics logTag], @"This transmission target is disabled."); return; @@ -288,23 +321,8 @@ - (void)trackEvent:(NSString *)eventName } log.typedProperties = [properties isEmpty] ? nil : properties; - //TODO Remove the workaround below once transmission targets support EventProperties. - /* - * If there are any target tokens, the typed properties must be moved into the old "properties" field. This can be removed once the One Collector - * logic is able to deal with the EventProperties object. Until then, this workaround prevents One Collector logs from breaking. - */ - if (log.typedProperties && [log.transmissionTargetTokens count] != 0) { - NSMutableDictionary *oldStyleStringProperties = [NSMutableDictionary new]; - for (MSTypedProperty *property in [log.typedProperties.properties objectEnumerator]) { - if ([property isKindOfClass:[MSStringTypedProperty class]]) { - oldStyleStringProperties[property.name] = ((MSStringTypedProperty *)property).value; - } - } - log.properties = oldStyleStringProperties; - } - // Send log to channel. - [self sendLog:log]; + [self sendLog:log flags:persistenceFlag]; } - (void)pause { @@ -354,7 +372,7 @@ - (void)trackPage:(NSString *)pageName withProperties:(NSDictionary)log { +- (void)sendLog:(id)log flags:(MSFlags)flags { // Send log to log manager. - [self.channelUnit enqueueItem:log]; + [self.channelUnit enqueueItem:log flags:flags]; } - (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)transmissionTargetToken { @@ -378,11 +396,7 @@ - (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)transm [MSUtility targetKeyFromTargetToken:transmissionTargetToken]); return transmissionTarget; } - transmissionTarget = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:transmissionTargetToken - parentTarget:nil - channelGroup:self.channelGroup]; - MSLogDebug([MSAnalytics logTag], @"Created transmission target with id %@.", - [MSUtility targetKeyFromTargetToken:transmissionTargetToken]); + transmissionTarget = [self createTransmissionTargetForToken:transmissionTargetToken]; self.transmissionTargets[transmissionTargetToken] = transmissionTarget; // TODO: Start service if not already. @@ -391,6 +405,15 @@ - (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)transm return transmissionTarget; } +- (MSAnalyticsTransmissionTarget *)createTransmissionTargetForToken:(NSString *)transmissionTargetToken { + MSAnalyticsTransmissionTarget *target = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:transmissionTargetToken + parentTarget:nil + channelGroup:self.channelGroup]; + MSLogDebug([MSAnalytics logTag], @"Created transmission target with target key %@.", + [MSUtility targetKeyFromTargetToken:transmissionTargetToken]); + return target; +} + - (void)pauseTransmissionTargetForToken:(NSString *)token { if (self.oneCollectorChannelUnit) { [self.oneCollectorChannelUnit pauseSendingLogsWithToken:token]; @@ -422,7 +445,7 @@ + (void)resetSharedInstance { - (void)sessionTracker:(id)sessionTracker processLog:(id)log { (void)sessionTracker; - [self sendLog:log]; + [self sendLog:log flags:MSFlagsDefault]; } + (void)setDelegate:(nullable id)delegate { @@ -455,8 +478,8 @@ - (void)channel:(id)channel didSucceedSendingLog:(id)l if ([logObject isKindOfClass:[MSEventLog class]] && [self.delegate respondsToSelector:@selector(analytics:didSucceedSendingEventLog:)]) { MSEventLog *eventLog = (MSEventLog *)log; [self.delegate analytics:self didSucceedSendingEventLog:eventLog]; - } else if ([logObject isKindOfClass:[MSPageLog class]] && - [self.delegate respondsToSelector:@selector(analytics:didSucceedSendingPageLog:)]) { + } else if ([logObject isKindOfClass:[MSPageLog class]] && [self.delegate respondsToSelector:@selector(analytics: + didSucceedSendingPageLog:)]) { MSPageLog *pageLog = (MSPageLog *)log; [self.delegate analytics:self didSucceedSendingPageLog:pageLog]; } @@ -468,12 +491,12 @@ - (void)channel:(id)channel didFailSendingLog:(id)log return; } NSObject *logObject = (NSObject *)log; - if ([logObject isKindOfClass:[MSEventLog class]] && - [self.delegate respondsToSelector:@selector(analytics:didFailSendingEventLog:withError:)]) { + if ([logObject isKindOfClass:[MSEventLog class]] && [self.delegate respondsToSelector:@selector(analytics: + didFailSendingEventLog:withError:)]) { MSEventLog *eventLog = (MSEventLog *)log; [self.delegate analytics:self didFailSendingEventLog:eventLog withError:error]; - } else if ([logObject isKindOfClass:[MSPageLog class]] && - [self.delegate respondsToSelector:@selector(analytics:didFailSendingPageLog:withError:)]) { + } else if ([logObject isKindOfClass:[MSPageLog class]] && [self.delegate respondsToSelector:@selector(analytics: + didFailSendingPageLog:withError:)]) { MSPageLog *pageLog = (MSPageLog *)log; [self.delegate analytics:self didFailSendingPageLog:pageLog withError:error]; } diff --git a/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.h b/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.h index aeaade4506..6640b3f0c0 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.h +++ b/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN * @param value Property value. * @param key Property key. */ -- (instancetype)setString:(NSString *)value forKey:(NSString *)key; +- (instancetype)setString:(NSString *)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:)); /** * Set a double property. @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN * @param value Property value. Must be finite (`NAN` and `INFINITY` not allowed). * @param key Property key. */ -- (instancetype)setDouble:(double)value forKey:(NSString *)key; +- (instancetype)setDouble:(double)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:)); /** * Set a 64-bit integer property. @@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN * @param value Property value. * @param key Property key. */ -- (instancetype)setInt64:(int64_t)value forKey:(NSString *)key; +- (instancetype)setInt64:(int64_t)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:)); /** * Set a boolean property. @@ -37,15 +37,15 @@ NS_ASSUME_NONNULL_BEGIN * @param value Property value. * @param key Property key. */ -- (instancetype)setBool:(BOOL)value forKey:(NSString *)key; +- (instancetype)setBool:(BOOL)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:)); /** - * Set a Date property. + * Set a date property. * * @param value Property value. * @param key Property key. */ -- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key; +- (instancetype)setDate:(NSDate *)value forKey:(NSString *)key NS_SWIFT_NAME(setEventProperty(_:forKey:)); @end diff --git a/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.m b/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.m index 0e51cca867..f487d4d700 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.m +++ b/AppCenterAnalytics/AppCenterAnalytics/MSEventProperties.m @@ -1,6 +1,5 @@ #import "MSAnalyticsInternal.h" #import "MSBooleanTypedProperty.h" -#import "MSConstants+Internal.h" #import "MSDateTimeTypedProperty.h" #import "MSDoubleTypedProperty.h" #import "MSEventProperties.h" @@ -33,7 +32,7 @@ - (instancetype)initWithStringDictionary:(NSDictionary * #pragma mark - NSCoding - (void)encodeWithCoder:(NSCoder *)coder { - @synchronized (self.properties) { + @synchronized(self.properties) { [coder encodeObject:self.properties]; } } @@ -48,29 +47,29 @@ - (instancetype)initWithCoder:(NSCoder *)coder { #pragma mark - Public methods - (instancetype)setString:(NSString *)value forKey:(NSString *)key { - if ([MSEventProperties validateKey:key] && [MSEventProperties validateValue:value]) { - MSStringTypedProperty *stringProperty = [MSStringTypedProperty new]; - stringProperty.name = key; - stringProperty.value = value; - @synchronized (self.properties) { - self.properties[key] = stringProperty; - } + if ([MSEventProperties validateKey:key] && [MSEventProperties validateValue:value]) { + MSStringTypedProperty *stringProperty = [MSStringTypedProperty new]; + stringProperty.name = key; + stringProperty.value = value; + @synchronized(self.properties) { + self.properties[key] = stringProperty; } - return self; + } + return self; } - (instancetype)setDouble:(double)value forKey:(NSString *)key { if ([MSEventProperties validateKey:key]) { // NaN returns false for all statements, so the only way to check if value is NaN is by value != value. - if (value == (double)INFINITY || value != value) { + if (value == (double)INFINITY || value == -(double)INFINITY || value != value) { MSLogError([MSAnalytics logTag], @"Double value for property '%@' must be finite (cannot be INFINITY or NAN).", key); return self; } MSDoubleTypedProperty *doubleProperty = [MSDoubleTypedProperty new]; doubleProperty.name = key; doubleProperty.value = value; - @synchronized (self.properties) { + @synchronized(self.properties) { self.properties[key] = doubleProperty; } } @@ -82,7 +81,7 @@ - (instancetype)setInt64:(int64_t)value forKey:(NSString *)key { MSLongTypedProperty *longProperty = [MSLongTypedProperty new]; longProperty.name = key; longProperty.value = value; - @synchronized (self.properties) { + @synchronized(self.properties) { self.properties[key] = longProperty; } } @@ -94,7 +93,7 @@ - (instancetype)setBool:(BOOL)value forKey:(NSString *)key { MSBooleanTypedProperty *boolProperty = [MSBooleanTypedProperty new]; boolProperty.name = key; boolProperty.value = value; - @synchronized (self.properties) { + @synchronized(self.properties) { self.properties[key] = boolProperty; } } @@ -106,7 +105,7 @@ - (instancetype)setDate:(NSDate *)value forKey:(NSString *)key { MSDateTimeTypedProperty *dateTimeProperty = [MSDateTimeTypedProperty new]; dateTimeProperty.name = key; dateTimeProperty.value = value; - @synchronized (self.properties) { + @synchronized(self.properties) { self.properties[key] = dateTimeProperty; } } @@ -117,18 +116,26 @@ - (instancetype)setDate:(NSDate *)value forKey:(NSString *)key { - (NSMutableArray *)serializeToArray { NSMutableArray *propertiesArray = [NSMutableArray new]; - @synchronized (self.properties) { - for (MSTypedProperty *typedProperty in [self.properties objectEnumerator]) { - [propertiesArray addObject:[typedProperty serializeToDictionary]]; - } + @synchronized(self.properties) { + for (MSTypedProperty *typedProperty in [self.properties objectEnumerator]) { + [propertiesArray addObject:[typedProperty serializeToDictionary]]; + } } return propertiesArray; } -- (BOOL) isEmpty { +- (BOOL)isEmpty { return [self.properties count] == 0; } +- (BOOL)isEqual:(id)object { + if (![(NSObject *)object isKindOfClass:[MSEventProperties class]]) { + return NO; + } + MSEventProperties *properties = (MSEventProperties *)object; + return ((!self.properties && !properties.properties) || [self.properties isEqualToDictionary:properties.properties]); +} + #pragma mark - Helper methods + (BOOL)validateKey:(NSString *)key { @@ -147,4 +154,8 @@ + (BOOL)validateValue:(NSObject *)value { return YES; } +- (void)mergeEventProperties:(MSEventProperties *__nonnull)eventProperties { + [self.properties addEntriesFromDictionary:(NSDictionary *)eventProperties.properties]; +} + @end diff --git a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.h b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.h index c5a8800fa1..00f275569c 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.h +++ b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.h @@ -1,8 +1,11 @@ #import #import "MSAnalyticsAuthenticationProvider.h" +#import "MSConstants+Flags.h" #import "MSPropertyConfigurator.h" +@class MSEventProperties; + NS_ASSUME_NONNULL_BEGIN @interface MSAnalyticsTransmissionTarget : NSObject @@ -30,6 +33,71 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties; +/** + * Track an event. + * + * @param eventName event name. + * @param properties dictionary of properties. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. + */ +- (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties flags:(MSFlags)flags; + +/** + * Track a custom event with name and optional typed properties. + * + * @param eventName Event name. + * @param properties Typed properties. + * + * @discussion The following validation rules are applied: + * + * The name cannot be null or empty. + * + * The property names or values cannot be null. + * + * Double values must be finite (NaN or Infinite values are discarded). + * + * Additional validation rules apply depending on the configured secret. + * + * - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression. + * + * - The `baseData` and `baseDataType` properties are reserved and thus discarded. + * + * - The full event size when encoded as a JSON string cannot be larger than 1.9MB. + */ +- (void)trackEvent:(NSString *)eventName + withTypedProperties:(nullable MSEventProperties *)properties NS_SWIFT_NAME(trackEvent(_:withProperties:)); + +/** + * Track a custom event with name and optional typed properties. + * + * @param eventName Event name. + * @param properties Typed properties. + * @param flags Optional flags. Events tracked with the MSFlagsPersistenceCritical flag will take precedence over all other events in + * storage. An event tracked with this option will only be dropped if storage must make room for a newer event that is also marked with the + * MSFlagsPersistenceCritical flag. + * + * @discussion The following validation rules are applied: + * + * The name cannot be null or empty. + * + * The property names or values cannot be null. + * + * Double values must be finite (NaN or Infinite values are discarded). + * + * Additional validation rules apply depending on the configured secret. + * + * - The event name needs to match the `[a-zA-Z0-9]((\.(?!(\.|$)))|[_a-zA-Z0-9]){3,99}` regular expression. + * + * - The `baseData` and `baseDataType` properties are reserved and thus discarded. + * + * - The full event size when encoded as a JSON string cannot be larger than 1.9MB. + */ +- (void)trackEvent:(NSString *)eventName + withTypedProperties:(nullable MSEventProperties *)properties + flags:(MSFlags)flags NS_SWIFT_NAME(trackEvent(_:withProperties:flags:)); + /** * Get a nested transmission target. * diff --git a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.m b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.m index 9725f3da6a..adeb4cdb4d 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.m +++ b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSAnalyticsTransmissionTarget.m @@ -2,10 +2,11 @@ #import "MSAnalyticsInternal.h" #import "MSAnalyticsTransmissionTargetInternal.h" #import "MSAnalyticsTransmissionTargetPrivate.h" -#import "MSCommonSchemaLog.h" #import "MSCSExtensions.h" +#import "MSCommonSchemaLog.h" +#import "MSEventPropertiesInternal.h" #import "MSLogger.h" -#import "MSPropertyConfiguratorPrivate.h" +#import "MSPropertyConfiguratorInternal.h" #import "MSProtocolExtension.h" #import "MSServiceAbstractInternal.h" #import "MSUtility+StringFormatting.h" @@ -55,40 +56,49 @@ + (void)addAuthenticationProvider:(MSAnalyticsAuthenticationProvider *)authentic } } -/** - * Track an event. - * - * @param eventName event name. - */ - (void)trackEvent:(NSString *)eventName { [self trackEvent:eventName withProperties:nil]; } -/** - * Track an event. - * - * @param eventName event name. - * @param properties dictionary of properties. - */ - (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties { - NSMutableDictionary *mergedProperties = [NSMutableDictionary new]; + [self trackEvent:eventName withProperties:properties flags:MSFlagsDefault]; +} + +- (void)trackEvent:(NSString *)eventName withProperties:(nullable NSDictionary *)properties flags:(MSFlags)flags { + MSEventProperties *eventProperties; + if (properties) { + eventProperties = [MSEventProperties new]; + for (NSString *key in properties.allKeys) { + NSString *value = properties[key]; + [eventProperties setString:value forKey:key]; + } + } + [self trackEvent:eventName withTypedProperties:eventProperties flags:flags]; +} + +- (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties { + [self trackEvent:eventName withTypedProperties:properties flags:MSFlagsDefault]; +} + +- (void)trackEvent:(NSString *)eventName withTypedProperties:(nullable MSEventProperties *)properties flags:(MSFlags)flags { + MSEventProperties *mergedProperties = [MSEventProperties new]; // Merge properties in its ancestors. MSAnalyticsTransmissionTarget *target = self; while (target != nil) { - [target mergeEventPropertiesWith:mergedProperties]; + [target.propertyConfigurator mergeTypedPropertiesWith:mergedProperties]; target = target.parentTarget; } // Override properties. if (properties) { - [mergedProperties addEntriesFromDictionary:(NSDictionary * _Nonnull)properties]; - } else if ([mergedProperties count] == 0) { + [mergedProperties mergeEventProperties:(MSEventProperties * __nonnull) properties]; + } else if ([mergedProperties isEmpty]) { // Set nil for the properties to pass nil to trackEvent. mergedProperties = nil; } - [MSAnalytics trackEvent:eventName withProperties:mergedProperties forTransmissionTarget:self]; + [MSAnalytics trackEvent:eventName withTypedProperties:mergedProperties forTransmissionTarget:self flags:flags]; } - (MSAnalyticsTransmissionTarget *)transmissionTargetForToken:(NSString *)token { @@ -189,17 +199,6 @@ + (void)setAuthenticationProvider:(MSAnalyticsAuthenticationProvider *)authentic } } -- (void)mergeEventPropertiesWith:(NSMutableDictionary *)mergedProperties { - @synchronized([MSAnalytics sharedInstance]) { - for (NSString *key in self.propertyConfigurator.eventProperties) { - if (mergedProperties[key] == nil) { - NSString *value = self.propertyConfigurator.eventProperties[key]; - mergedProperties[key] = value; - } - } - } -} - /** * Check ancestor enabled state, the ancestor is either the immediate target parent if there is one or Analytics. * diff --git a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.h b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.h index b20ea5d429..8db3ea4f3d 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.h +++ b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.h @@ -26,16 +26,64 @@ NS_ASSUME_NONNULL_BEGIN - (void)setAppLocale:(nullable NSString *)appLocale; /** - * Set an event property to be attached to events tracked by this transmission target and its child transmission targets. + * Set a string event property to be attached to events tracked by this transmission target and its child transmission targets. * * @param propertyValue Property value. * @param propertyKey Property key. * * @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the - * properties passed to the `trackEvent:withProperties:` override any property with the same key from the transmission target itself or its - * parents. + * properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from + * the transmission target itself or its parents. */ -- (void)setEventPropertyString:(NSString *)propertyValue forKey:(NSString *)propertyKey; +- (void)setEventPropertyString:(NSString *)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:)); + +/** + * Set a double event property to be attached to events tracked by this transmission target and its child transmission targets. + * + * @param propertyValue Property value. Must be finite (`NAN` and `INFINITY` not allowed). + * @param propertyKey Property key. + * + * @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the + * properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from + * the transmission target itself or its parents. + */ +- (void)setEventPropertyDouble:(double)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:)); + +/** + * Set a 64-bit integer event property to be attached to events tracked by this transmission target and its child transmission targets. + * + * @param propertyValue Property value. + * @param propertyKey Property key. + * + * @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the + * properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from + * the transmission target itself or its parents. + */ +- (void)setEventPropertyInt64:(int64_t)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:)); + +/** + * Set a boolean event property to be attached to events tracked by this transmission target and its child transmission targets. + * + * @param propertyValue Property value. + * @param propertyKey Property key. + * + * @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the + * properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from + * the transmission target itself or its parents. + */ +- (void)setEventPropertyBool:(BOOL)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:)); + +/** + * Set a date event property to be attached to events tracked by this transmission target and its child transmission targets. + * + * @param propertyValue Property value. + * @param propertyKey Property key. + * + * @discussion A property set in a child transmission target overrides a property with the same key inherited from its parents. Also, the + * properties passed to the `trackEvent:withProperties:` or `trackEvent:withTypedProperties:` override any property with the same key from + * the transmission target itself or its parents. + */ +- (void)setEventPropertyDate:(NSDate *)propertyValue forKey:(NSString *)propertyKey NS_SWIFT_NAME(setEventProperty(_:forKey:)); /** * Remove an event property from this transmission target. @@ -44,7 +92,7 @@ NS_ASSUME_NONNULL_BEGIN * * @discussion This won't remove properties with the same name declared in other nested transmission targets. */ -- (void)removeEventPropertyForKey:(NSString *)propertyKey; +- (void)removeEventPropertyForKey:(NSString *)propertyKey NS_SWIFT_NAME(removeEventProperty(forKey:)); /** * Once called, the App Center SDK will automatically add UIDevice.identifierForVendor to common schema logs. diff --git a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.m b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.m index 8380c9b380..2762d8055a 100644 --- a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.m +++ b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfigurator.m @@ -10,11 +10,13 @@ #import "MSAnalyticsTransmissionTargetInternal.h" #import "MSAnalyticsTransmissionTargetPrivate.h" #import "MSAppExtension.h" -#import "MSCommonSchemaLog.h" #import "MSCSExtensions.h" +#import "MSCommonSchemaLog.h" #import "MSDeviceExtension.h" +#import "MSEventPropertiesInternal.h" #import "MSLogger.h" #import "MSPropertyConfiguratorPrivate.h" +#import "MSStringTypedProperty.h" @implementation MSPropertyConfigurator @@ -27,7 +29,7 @@ @implementation MSPropertyConfigurator - (instancetype)initWithTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget { if ((self = [super init])) { _transmissionTarget = transmissionTarget; - _eventProperties = [NSMutableDictionary new]; + _eventProperties = [MSEventProperties new]; } return self; } @@ -46,11 +48,31 @@ - (void)setAppLocale:(NSString *)appLocale { - (void)setEventPropertyString:(NSString *)propertyValue forKey:(NSString *)propertyKey { @synchronized([MSAnalytics sharedInstance]) { - if (!propertyValue || !propertyKey) { - MSLogError([MSAnalytics logTag], @"Event property keys and values cannot be nil."); - return; - } - self.eventProperties[propertyKey] = propertyValue; + [self.eventProperties setString:propertyValue forKey:propertyKey]; + } +} + +- (void)setEventPropertyDouble:(double)propertyValue forKey:(NSString *)propertyKey { + @synchronized([MSAnalytics sharedInstance]) { + [self.eventProperties setDouble:propertyValue forKey:propertyKey]; + } +} + +- (void)setEventPropertyInt64:(int64_t)propertyValue forKey:(NSString *)propertyKey { + @synchronized([MSAnalytics sharedInstance]) { + [self.eventProperties setInt64:propertyValue forKey:propertyKey]; + } +} + +- (void)setEventPropertyBool:(BOOL)propertyValue forKey:(NSString *)propertyKey { + @synchronized([MSAnalytics sharedInstance]) { + [self.eventProperties setBool:propertyValue forKey:propertyKey]; + } +} + +- (void)setEventPropertyDate:(NSDate *)propertyValue forKey:(NSString *)propertyKey { + @synchronized([MSAnalytics sharedInstance]) { + [self.eventProperties setDate:propertyValue forKey:propertyKey]; } } @@ -60,7 +82,7 @@ - (void)removeEventPropertyForKey:(NSString *)propertyKey { MSLogError([MSAnalytics logTag], @"Event property key to remove cannot be nil."); return; } - [self.eventProperties removeObjectForKey:propertyKey]; + [self.eventProperties.properties removeObjectForKey:propertyKey]; } } @@ -72,14 +94,7 @@ - (void)collectDeviceId { - (void)channel:(id)__unused channel prepareLog:(id)log { MSAnalyticsTransmissionTarget *target = self.transmissionTarget; - if (target && [log isKindOfClass:[MSCommonSchemaLog class]] && [target isEnabled]) { - - // TODO Find a better way to override properties. - - // Only override properties for owned target. - if (![log.transmissionTargetTokens containsObject:target.transmissionTargetToken]) { - return; - } + if (target && [log isKindOfClass:[MSCommonSchemaLog class]] && [target isEnabled] && [log.tag isEqual:target]) { // Override the application version. while (target) { @@ -111,7 +126,7 @@ - (void)channel:(id)__unused channel prepareLog:(id)lo } // The device ID must not be inherited from parent transmission targets. - [((MSCommonSchemaLog *)log)ext].deviceExt.localId = self.deviceId; + [((MSCommonSchemaLog *)log) ext].deviceExt.localId = self.deviceId; } } @@ -145,4 +160,14 @@ + (NSString *)getDeviceIdentifier { return [NSString stringWithFormat:@"%c:%@", deviceIdPrefix, baseIdentifier]; } +- (void)mergeTypedPropertiesWith:(MSEventProperties *)mergedEventProperties { + @synchronized([MSAnalytics sharedInstance]) { + for (NSString *key in self.eventProperties.properties) { + if (!mergedEventProperties.properties[key]) { + mergedEventProperties.properties[key] = self.eventProperties.properties[key]; + } + } + } +} + @end diff --git a/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfiguratorInternal.h b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfiguratorInternal.h new file mode 100644 index 0000000000..28026ae5bf --- /dev/null +++ b/AppCenterAnalytics/AppCenterAnalytics/TransmissionTarget/MSPropertyConfiguratorInternal.h @@ -0,0 +1,24 @@ +#import + +#import "MSChannelDelegate.h" +#import "MSEventPropertiesInternal.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MSPropertyConfigurator () + +/** + * Initialize property configurator with a transmission target. + */ +- (instancetype)initWithTransmissionTarget:(MSAnalyticsTransmissionTarget *)transmissionTarget; + +/** + * Merge typed properties. + * + * @param mergedEventProperties The destination event properties that merges current event properties to. + */ +- (void)mergeTypedPropertiesWith:(MSEventProperties *)mergedEventProperties; + +NS_ASSUME_NONNULL_END + +@end diff --git a/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTests.m b/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTests.m index 09db6f9ff3..a4751ad2f0 100644 --- a/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTests.m +++ b/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTests.m @@ -180,10 +180,10 @@ - (void)testAnalyticsDelegateWithoutImplementations { OCMReject([delegateMock analytics:[MSAnalytics sharedInstance] didFailSendingEventLog:eventLog withError:nil]); [MSAppCenter sharedInstance].sdkConfigured = NO; [MSAppCenter sharedInstance].configuredFromApplication = NO; - [MSAppCenter start:kMSTestAppSecret withServices:@[ [MSAnalytics class] ]]; + [MSAppCenter start:kMSTestAppSecret withServices:@ [[MSAnalytics class]]]; MSChannelUnitDefault *channelMock = OCMPartialMock([MSAnalytics sharedInstance].channelUnit); [MSAnalytics sharedInstance].channelUnit = channelMock; - OCMStub([channelMock enqueueItem:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + OCMStub([channelMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { id log = nil; [invocation getArgument:&log atIndex:2]; for (id delegate in channelMock.delegates) { @@ -196,7 +196,7 @@ - (void)testAnalyticsDelegateWithoutImplementations { }); // When - [[MSAnalytics sharedInstance].channelUnit enqueueItem:eventLog]; + [[MSAnalytics sharedInstance].channelUnit enqueueItem:eventLog flags:MSFlagsDefault]; // Then OCMVerifyAll(delegateMock); @@ -209,10 +209,10 @@ - (void)testAnalyticsDelegateMethodsAreCalled { id delegateMock = OCMProtocolMock(@protocol(MSAnalyticsDelegate)); [MSAppCenter sharedInstance].sdkConfigured = NO; [MSAppCenter sharedInstance].configuredFromApplication = NO; - [MSAppCenter start:kMSTestAppSecret withServices:@[ [MSAnalytics class] ]]; + [MSAppCenter start:kMSTestAppSecret withServices:@ [[MSAnalytics class]]]; MSChannelUnitDefault *channelMock = OCMPartialMock([MSAnalytics sharedInstance].channelUnit); [MSAnalytics sharedInstance].channelUnit = channelMock; - OCMStub([channelMock enqueueItem:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + OCMStub([channelMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { id log = nil; [invocation getArgument:&log atIndex:2]; for (id delegate in channelMock.delegates) { @@ -227,7 +227,7 @@ - (void)testAnalyticsDelegateMethodsAreCalled { // When [[MSAnalytics sharedInstance] setDelegate:delegateMock]; MSEventLog *eventLog = OCMClassMock([MSEventLog class]); - [[MSAnalytics sharedInstance].channelUnit enqueueItem:eventLog]; + [[MSAnalytics sharedInstance].channelUnit enqueueItem:eventLog flags:MSFlagsDefault]; // Then OCMVerify([delegateMock analytics:[MSAnalytics sharedInstance] willSendEventLog:eventLog]); @@ -240,7 +240,7 @@ - (void)testAnalyticsLogsVerificationIsCalled { // If MSEventLog *eventLog = [MSEventLog new]; eventLog.name = @"test"; - eventLog.properties = @{ @"test" : @"test" }; + eventLog.properties = @{@"test" : @"test"}; MSPageLog *pageLog = [MSPageLog new]; MSLogWithNameAndProperties *analyticsLog = [MSLogWithNameAndProperties new]; id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); @@ -270,12 +270,13 @@ - (void)testTrackEventWithoutProperties { id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - MSEventLog *log; - [invocation getArgument:&log atIndex:2]; - type = log.type; - name = log.name; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + name = log.name; + }); [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock appSecret:kMSTestAppSecret @@ -305,8 +306,8 @@ - (void)testTrackEventWithPropertiesNilWhenAnalyticsDisabled { fromApplication:YES]; // When - OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY]); - [[MSAnalytics sharedInstance] trackEvent:@"Some event" withProperties:nil forTransmissionTarget:nil]; + OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); + [[MSAnalytics sharedInstance] trackEvent:@"Some event" withProperties:nil forTransmissionTarget:nil flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); @@ -327,8 +328,8 @@ - (void)testTrackEventWithTypedPropertiesNilWhenAnalyticsDisabled { fromApplication:YES]; // When - OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY]); - [[MSAnalytics sharedInstance] trackEvent:@"Some event" withTypedProperties:nil forTransmissionTarget:nil]; + OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); + [[MSAnalytics sharedInstance] trackEvent:@"Some event" withTypedProperties:nil forTransmissionTarget:nil flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); @@ -347,15 +348,41 @@ - (void)testTrackEventWithPropertiesNilWhenTransmissionTargetDisabled { fromApplication:YES]; // When - OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); MSAnalyticsTransmissionTarget *target = [MSAnalytics transmissionTargetForToken:@"test"]; [target setEnabled:NO]; - [[MSAnalytics sharedInstance] trackEvent:@"Some event" withProperties:nil forTransmissionTarget:target]; + [[MSAnalytics sharedInstance] trackEvent:@"Some event" withProperties:nil forTransmissionTarget:target flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); } +- (void)testTrackEventSetsTagWhenTransmissionTargetProvided { + + // If + __block NSObject *tag; + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + tag = log.tag; + }); + + // When + MSAnalyticsTransmissionTarget *target = [MSAnalytics transmissionTargetForToken:@"test"]; + [[MSAnalytics sharedInstance] trackEvent:@"Some event" withProperties:nil forTransmissionTarget:target flags:MSFlagsDefault]; + + // Then + XCTAssertEqualObjects(tag, target); +} + - (void)testTrackEventWithTypedPropertiesNilWhenTransmissionTargetDisabled { // If @@ -369,10 +396,10 @@ - (void)testTrackEventWithTypedPropertiesNilWhenTransmissionTargetDisabled { fromApplication:YES]; // When - OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); MSAnalyticsTransmissionTarget *target = [MSAnalytics transmissionTargetForToken:@"test"]; [target setEnabled:NO]; - [[MSAnalytics sharedInstance] trackEvent:@"Some event" withTypedProperties:nil forTransmissionTarget:target]; + [[MSAnalytics sharedInstance] trackEvent:@"Some event" withTypedProperties:nil forTransmissionTarget:target flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); @@ -393,12 +420,12 @@ - (void)testTrackEventWithPropertiesNilAndInvalidName { fromApplication:YES]; // When - OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); // Will be validated in shouldFilterLog callback instead. OCMReject([analyticsMock validateEventName:OCMOCK_ANY forLogType:OCMOCK_ANY]); OCMReject([analyticsMock validateProperties:OCMOCK_ANY forLogName:OCMOCK_ANY andType:OCMOCK_ANY]); - [[MSAnalytics sharedInstance] trackEvent:invalidEventName withProperties:nil forTransmissionTarget:nil]; + [[MSAnalytics sharedInstance] trackEvent:invalidEventName withProperties:nil forTransmissionTarget:nil flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); @@ -420,12 +447,12 @@ - (void)testTrackEventWithTypedPropertiesNilAndInvalidName { fromApplication:YES]; // When - OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); // Will be validated in shouldFilterLog callback instead. OCMReject([analyticsMock validateEventName:OCMOCK_ANY forLogType:OCMOCK_ANY]); OCMReject([analyticsMock validateProperties:OCMOCK_ANY forLogName:OCMOCK_ANY andType:OCMOCK_ANY]); - [[MSAnalytics sharedInstance] trackEvent:invalidEventName withTypedProperties:nil forTransmissionTarget:nil]; + [[MSAnalytics sharedInstance] trackEvent:invalidEventName withTypedProperties:nil forTransmissionTarget:nil flags:MSFlagsDefault]; // Then OCMVerifyAll(channelUnitMock); @@ -439,11 +466,11 @@ - (void)testTrackEventWithProperties { __block NSString *name; __block MSEventProperties *eventProperties; NSString *expectedName = @"gotACoffee"; - NSDictionary *expectedProperties = @{ @"milk" : @"yes", @"cookie" : @"of course" }; + NSDictionary *expectedProperties = @{@"milk" : @"yes", @"cookie" : @"of course"}; id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]]]).andDo(^(NSInvocation *invocation) { + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { MSEventLog *log; [invocation getArgument:&log atIndex:2]; type = log.type; @@ -464,7 +491,7 @@ - (void)testTrackEventWithProperties { assertThat(name, is(expectedName)); for (MSTypedProperty *typedProperty in [eventProperties.properties objectEnumerator]) { assertThat(typedProperty, isA([MSStringTypedProperty class])); - MSStringTypedProperty *stringTypedProperty = (MSStringTypedProperty*)typedProperty; + MSStringTypedProperty *stringTypedProperty = (MSStringTypedProperty *)typedProperty; assertThat(stringTypedProperty.value, equalTo(expectedProperties[stringTypedProperty.name])); } XCTAssertEqual([expectedProperties count], [eventProperties.properties count]); @@ -486,7 +513,7 @@ - (void)testTrackEventWithTypedProperties { id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]]]).andDo(^(NSInvocation *invocation) { + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { MSEventLog *log; [invocation getArgument:&log atIndex:2]; type = log.type; @@ -535,6 +562,216 @@ - (void)testTrackEventWithTypedProperties { XCTAssertEqual([expectedProperties.properties count], 0); } +- (void)testTrackEventWithPropertiesWithNormalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withProperties:nil flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithPropertiesWithCriticalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withProperties:nil flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceCritical); +} + +- (void)testTrackEventWithPropertiesWithInvalidFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withProperties:nil flags:42]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithTypedPropertiesWithNormalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withTypedProperties:nil flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithTypedPropertiesWithCriticalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withTypedProperties:nil flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceCritical); +} + +- (void)testTrackEventWithTypedPropertiesWithInvalidFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"gotACoffee"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock + appSecret:kMSTestAppSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [MSAnalytics trackEvent:expectedName withTypedProperties:nil flags:42]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + - (void)testTrackPageWithoutProperties { // If @@ -544,12 +781,13 @@ - (void)testTrackPageWithoutProperties { id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - MSEventLog *log; - [invocation getArgument:&log atIndex:2]; - type = log.type; - name = log.name; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + name = log.name; + }); [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock appSecret:kMSTestAppSecret @@ -571,17 +809,18 @@ - (void)testTrackPageWithProperties { __block NSString *name; __block NSDictionary *properties; NSString *expectedName = @"HomeSweetHome"; - NSDictionary *expectedProperties = @{ @"Sofa" : @"yes", @"TV" : @"of course" }; + NSDictionary *expectedProperties = @{@"Sofa" : @"yes", @"TV" : @"of course"}; id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - MSEventLog *log; - [invocation getArgument:&log atIndex:2]; - type = log.type; - name = log.name; - properties = log.properties; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + name = log.name; + properties = log.properties; + }); [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock appSecret:kMSTestAppSecret @@ -613,7 +852,7 @@ - (void)testTrackPageWhenAnalyticsDisabled { fromApplication:YES]; // When - OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMReject([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); [[MSAnalytics sharedInstance] trackPage:@"Some page" withProperties:nil]; // Then @@ -635,7 +874,7 @@ - (void)testTrackPageWithInvalidName { fromApplication:YES]; // When - OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY]); + OCMExpect([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]); // Will be validated in shouldFilterLog callback instead. OCMReject([analyticsMock validateEventName:OCMOCK_ANY forLogType:OCMOCK_ANY]); @@ -774,10 +1013,11 @@ - (void)testStartWithTransmissionTargetAndAppSecretUsesTransmissionTarget { __block MSEventLog *log; __block int invocations = 0; OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - ++invocations; - [invocation getArgument:&log atIndex:2]; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + ++invocations; + [invocation getArgument:&log atIndex:2]; + }); [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock appSecret:kMSTestAppSecret transmissionTargetToken:kMSTestTransmissionToken @@ -787,7 +1027,7 @@ - (void)testStartWithTransmissionTargetAndAppSecretUsesTransmissionTarget { [MSAnalytics trackEvent:@"eventName"]; // Then - OCMVerify([channelUnitMock enqueueItem:log]); + OCMVerify([channelUnitMock enqueueItem:log flags:MSFlagsDefault]); XCTAssertTrue([[log transmissionTargetTokens] containsObject:kMSTestTransmissionToken]); XCTAssertEqual([[log transmissionTargetTokens] count], (unsigned long)1); XCTAssertEqual(invocations, 1); @@ -802,10 +1042,11 @@ - (void)testStartWithTransmissionTargetWithoutAppSecretUsesTransmissionTarget { __block MSEventLog *log; __block int invocations = 0; OCMStub([channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - ++invocations; - [invocation getArgument:&log atIndex:2]; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + ++invocations; + [invocation getArgument:&log atIndex:2]; + }); [[MSAnalytics sharedInstance] startWithChannelGroup:channelGroupMock appSecret:nil transmissionTargetToken:kMSTestTransmissionToken @@ -815,7 +1056,7 @@ - (void)testStartWithTransmissionTargetWithoutAppSecretUsesTransmissionTarget { [MSAnalytics trackEvent:@"eventName"]; // Then - OCMVerify([channelUnitMock enqueueItem:log]); + OCMVerify([channelUnitMock enqueueItem:log flags:MSFlagsDefault]); XCTAssertTrue([[log transmissionTargetTokens] containsObject:kMSTestTransmissionToken]); XCTAssertEqual([[log transmissionTargetTokens] count], (unsigned long)1); XCTAssertEqual(invocations, 1); @@ -832,6 +1073,23 @@ - (void)testGetTransmissionTargetCreatesTransmissionTargetOnce { XCTAssertEqual(transmissionTarget1, transmissionTarget2); } +- (void)testGetTransmissionTargetNeverReturnsDefault { + + // If + [[MSAnalytics sharedInstance] startWithChannelGroup:OCMProtocolMock(@protocol(MSChannelGroupProtocol)) + appSecret:nil + transmissionTargetToken:kMSTestTransmissionToken + fromApplication:NO]; + + // When + MSAnalyticsTransmissionTarget *transmissionTarget = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; + + // Then + XCTAssertNotNil([MSAnalytics sharedInstance].defaultTransmissionTarget); + XCTAssertNotNil(transmissionTarget); + XCTAssertNotEqual([MSAnalytics sharedInstance].defaultTransmissionTarget, transmissionTarget); +} + - (void)testEnableStatePropagateToTransmissionTargets { // If @@ -889,13 +1147,13 @@ - (void)testSessionTrackerStarted { [MSAppCenter resetSharedInstance]; // When - [MSAppCenter startFromLibraryWithServices:@[ [MSAnalytics class] ]]; + [MSAppCenter startFromLibraryWithServices:@ [[MSAnalytics class]]]; // Then XCTAssertFalse([MSAnalytics sharedInstance].sessionTracker.started); // When - [MSAppCenter start:MS_UUID_STRING withServices:@[ [MSAnalytics class] ]]; + [MSAppCenter start:MS_UUID_STRING withServices:@ [[MSAnalytics class]]]; // Then XCTAssertTrue([MSAnalytics sharedInstance].sessionTracker.started); @@ -940,7 +1198,7 @@ - (void)testAutoPageTrackingWhenStartedFromLibrary { - (void)testRemoveInvalidPropertiesWithEmptyValue { // If - NSDictionary *emptyValueProperties = @{ @"aValidKey" : @"" }; + NSDictionary *emptyValueProperties = @{@"aValidKey" : @""}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:emptyValueProperties]; @@ -953,7 +1211,7 @@ - (void)testRemoveInvalidPropertiesWithEmptyValue { - (void)testRemoveInvalidPropertiesWithEmptyKey { // If - NSDictionary *emptyKeyProperties = @{ @"" : @"aValidValue" }; + NSDictionary *emptyKeyProperties = @{@"" : @"aValidValue"}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:emptyKeyProperties]; @@ -965,7 +1223,7 @@ - (void)testRemoveInvalidPropertiesWithEmptyKey { - (void)testremoveInvalidPropertiesWithNonStringKey { // If - NSDictionary *numberAsKeyProperties = @{ @(42) : @"aValidValue" }; + NSDictionary *numberAsKeyProperties = @{@(42) : @"aValidValue"}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:numberAsKeyProperties]; @@ -977,7 +1235,7 @@ - (void)testremoveInvalidPropertiesWithNonStringKey { - (void)testValidateLogDataWithNonStringValue { // If - NSDictionary *numberAsValueProperties = @{ @"aValidKey" : @(42) }; + NSDictionary *numberAsValueProperties = @{@"aValidKey" : @(42)}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:numberAsValueProperties]; @@ -989,7 +1247,7 @@ - (void)testValidateLogDataWithNonStringValue { - (void)testValidateLogDataWithCorrectNestedProperties { // If - NSDictionary *correctlyNestedProperties = @{ @"aValidKey1" : @"aValidValue1", @"aValidKey2.aValidKey2" : @"aValidValue3" }; + NSDictionary *correctlyNestedProperties = @{@"aValidKey1" : @"aValidValue1", @"aValidKey2.aValidKey2" : @"aValidValue3"}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:correctlyNestedProperties]; @@ -1021,7 +1279,7 @@ - (void)testValidateLogDataWithIncorrectNestedProperties { - (void)testDictionaryContainsInvalidPropertiesKey { // If - NSDictionary *incorrectNestedProperties = @{ @1 : @"aValidValue1", @"aValidKey2" : @"aValidValue2" }; + NSDictionary *incorrectNestedProperties = @{@1 : @"aValidValue1", @"aValidKey2" : @"aValidValue2"}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:incorrectNestedProperties]; @@ -1032,7 +1290,7 @@ - (void)testDictionaryContainsInvalidPropertiesKey { } - (void)testDictionaryContainsValidNestedProperties { - NSDictionary *properties = @{ @"aValidKey2" : @"aValidValue1", @"aValidKey1.avalidKey2" : @"aValidValue1" }; + NSDictionary *properties = @{@"aValidKey2" : @"aValidValue1", @"aValidKey1.avalidKey2" : @"aValidValue1"}; // When NSDictionary *result = [[MSAnalytics sharedInstance] removeInvalidProperties:properties]; diff --git a/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTransmissionTargetTests.m b/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTransmissionTargetTests.m index 982f4f7cb3..e4834d8a42 100644 --- a/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTransmissionTargetTests.m +++ b/AppCenterAnalytics/AppCenterAnalyticsTests/MSAnalyticsTransmissionTargetTests.m @@ -5,15 +5,21 @@ #import "MSAnalyticsTransmissionTargetPrivate.h" #import "MSAppCenterInternal.h" #import "MSAppExtension.h" -#import "MSChannelUnitDefault.h" +#import "MSBooleanTypedProperty.h" #import "MSCSExtensions.h" +#import "MSChannelUnitDefault.h" +#import "MSDateTimeTypedProperty.h" +#import "MSDoubleTypedProperty.h" #import "MSEventLog.h" #import "MSEventPropertiesInternal.h" +#import "MSLongTypedProperty.h" #import "MSMockUserDefaults.h" +#import "MSPropertyConfiguratorInternal.h" #import "MSPropertyConfiguratorPrivate.h" #import "MSStringTypedProperty.h" #import "MSTestFrameworks.h" +static NSString *const kMSTypeEvent = @"event"; static NSString *const kMSTestTransmissionToken = @"TestTransmissionToken"; static NSString *const kMSTestTransmissionToken2 = @"TestTransmissionToken2"; @@ -34,9 +40,15 @@ - (void)setUp { self.settingsMock = [MSMockUserDefaults new]; // Analytics enabled state can prevent targets from tracking events. - self.analyticsClassMock = OCMClassMock([MSAnalytics class]); - OCMStub(ClassMethod([self.analyticsClassMock isEnabled])).andReturn(YES); + id analyticsClassMock = OCMClassMock([MSAnalytics class]); + self.analyticsClassMock = OCMPartialMock([MSAnalytics sharedInstance]); + OCMStub([analyticsClassMock sharedInstance]).andReturn(self.analyticsClassMock); self.channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); + [MSAppCenter sharedInstance].sdkConfigured = YES; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:@"appsecret" + transmissionTargetToken:@"token" + fromApplication:YES]; } - (void)tearDown { @@ -61,7 +73,7 @@ - (void)testInitialization { // Then XCTAssertNotNil(sut); XCTAssertEqual(kMSTestTransmissionToken, sut.transmissionTargetToken); - XCTAssertEqualObjects(sut.propertyConfigurator.eventProperties, @{}); + XCTAssertTrue([sut.propertyConfigurator.eventProperties isEmpty]); XCTAssertNil(MSAnalyticsTransmissionTarget.authenticationProvider); } @@ -77,8 +89,8 @@ - (void)testTrackEvent { [sut trackEvent:eventName]; // Then - XCTAssertTrue(sut.propertyConfigurator.eventProperties.count == 0); - OCMVerify(ClassMethod([self.analyticsClassMock trackEvent:eventName withProperties:nil forTransmissionTarget:sut])); + XCTAssertTrue(sut.propertyConfigurator.eventProperties.properties.count == 0); + OCMVerify([self.analyticsClassMock trackEvent:eventName withTypedProperties:nil forTransmissionTarget:sut flags:MSFlagsDefault]); } - (void)testTrackEventWithProperties { @@ -88,20 +100,290 @@ - (void)testTrackEventWithProperties { parentTarget:nil channelGroup:self.channelGroupMock]; NSString *eventName = @"event"; - NSDictionary *properties = @{ @"prop1" : @"val1", @"prop2" : @"val2" }; + NSDictionary *properties = @{@"prop1" : @"val1", @"prop2" : @"val2"}; + MSEventProperties *expectedProperties = [MSEventProperties new]; + for (NSString *key in properties.allKeys) { + [expectedProperties setString:properties[key] forKey:key]; + } // When [sut trackEvent:eventName withProperties:properties]; // Then - XCTAssertTrue(sut.propertyConfigurator.eventProperties.count == 0); - OCMVerify(ClassMethod([self.analyticsClassMock trackEvent:eventName withProperties:properties forTransmissionTarget:sut])); + XCTAssertTrue(sut.propertyConfigurator.eventProperties.properties.count == 0); + OCMVerify([self.analyticsClassMock trackEvent:eventName + withTypedProperties:expectedProperties + forTransmissionTarget:sut + flags:MSFlagsDefault]); +} + +- (void)testTrackEventWithNilDictionaryProperties { + + // If + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *eventName = @"event"; + OCMStub([self.analyticsClassMock canBeUsed]).andReturn(YES); + + // When + [sut trackEvent:eventName withProperties:nil]; + + // Then + XCTAssertTrue(sut.propertyConfigurator.eventProperties.properties.count == 0); + OCMVerify([self.analyticsClassMock trackEvent:eventName withTypedProperties:nil forTransmissionTarget:sut flags:MSFlagsDefault]); +} + +- (void)testTrackEventWithNilEventProperties { + + // If + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *eventName = @"event"; + OCMStub([self.analyticsClassMock canBeUsed]).andReturn(YES); + + // When + [sut trackEvent:eventName withTypedProperties:nil]; + + // Then + XCTAssertTrue(sut.propertyConfigurator.eventProperties.properties.count == 0); + OCMVerify([self.analyticsClassMock trackEvent:eventName withTypedProperties:nil forTransmissionTarget:sut flags:MSFlagsDefault]); +} + +- (void)testTrackEventWithPropertiesWithNormalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withProperties:nil flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithPropertiesWithCriticalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withProperties:nil flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceCritical); +} + +- (void)testTrackEventWithPropertiesWithInvalidFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withProperties:nil flags:42]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithTypedPropertiesWithNormalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withTypedProperties:nil flags:MSFlagsPersistenceNormal]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); +} + +- (void)testTrackEventWithTypedPropertiesWithCriticalPersistenceFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withTypedProperties:nil flags:MSFlagsPersistenceCritical]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceCritical); +} + +- (void)testTrackEventWithTypedPropertiesWithInvalidFlag { + + // If + __block NSString *actualType; + __block NSString *actualName; + __block MSFlags actualFlags; + NSString *expectedName = @"event"; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + OCMStub([[channelUnitMock ignoringNonObjectArgs] enqueueItem:[OCMArg isKindOfClass:[MSEventLog class]] flags:(MSFlags)0]) + .andDo(^(NSInvocation *invocation) { + MSEventLog *log; + [invocation getArgument:&log atIndex:2]; + actualType = log.type; + actualName = log.name; + MSFlags flags; + [invocation getArgument:&flags atIndex:3]; + actualFlags = flags; + }); + MSAnalyticsTransmissionTarget *sut = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *appSecret = MS_UUID_STRING; + [MSAppCenter configureWithAppSecret:appSecret]; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:appSecret + transmissionTargetToken:nil + fromApplication:YES]; + + // When + [sut trackEvent:expectedName withTypedProperties:nil flags:42]; + + // Then + XCTAssertEqual(actualType, kMSTypeEvent); + XCTAssertEqual(actualName, expectedName); + XCTAssertEqual(actualFlags, MSFlagsPersistenceNormal); } - (void)testTransmissionTargetForToken { // If NSDictionary *properties = [NSDictionary new]; + MSEventProperties *emptyProperties = [MSEventProperties new]; NSString *event1 = @"event1"; NSString *event2 = @"event2"; NSString *event3 = @"event3"; @@ -135,28 +417,44 @@ - (void)testTransmissionTargetForToken { // Then XCTAssertNotEqualObjects(parentTransmissionTarget, childTransmissionTarget3); XCTAssertEqualObjects(childTransmissionTarget3, parentTransmissionTarget.childTransmissionTargets[kMSTestTransmissionToken]); - OCMVerify( - ClassMethod([self.analyticsClassMock trackEvent:event1 withProperties:properties forTransmissionTarget:childTransmissionTarget])); - OCMVerify( - ClassMethod([self.analyticsClassMock trackEvent:event2 withProperties:properties forTransmissionTarget:childTransmissionTarget2])); - OCMVerify( - ClassMethod([self.analyticsClassMock trackEvent:event3 withProperties:properties forTransmissionTarget:childTransmissionTarget3])); + OCMVerify([self.analyticsClassMock trackEvent:event1 + withTypedProperties:emptyProperties + forTransmissionTarget:childTransmissionTarget + flags:MSFlagsDefault]); + OCMVerify([self.analyticsClassMock trackEvent:event2 + withTypedProperties:emptyProperties + forTransmissionTarget:childTransmissionTarget2 + flags:MSFlagsDefault]); + OCMVerify([self.analyticsClassMock trackEvent:event3 + withTypedProperties:emptyProperties + forTransmissionTarget:childTransmissionTarget3 + flags:MSFlagsDefault]); } - (void)testTransmissionTargetEnabledState { // If - NSDictionary *properties = @{ @"prop1" : @"val1", @"prop2" : @"val2" }; + NSDictionary *properties = @{@"prop1" : @"val1", @"prop2" : @"val2"}; + MSEventProperties *expectedProperties = [MSEventProperties new]; + for (NSString *key in properties.allKeys) { + [expectedProperties setString:properties[key] forKey:key]; + } NSString *event1 = @"event1"; NSString *event2 = @"event2"; NSString *event3 = @"event3"; NSString *event4 = @"event4"; - MSAnalyticsTransmissionTarget *transmissionTarget, *transmissionTarget2; + OCMStub([self.analyticsClassMock canBeUsed]).andReturn(YES); // Events tracked when disabled mustn't be sent. - OCMReject(ClassMethod([self.analyticsClassMock trackEvent:event2 withProperties:properties forTransmissionTarget:transmissionTarget])); - OCMReject(ClassMethod([self.analyticsClassMock trackEvent:event3 withProperties:properties forTransmissionTarget:transmissionTarget2])); + OCMReject([self.analyticsClassMock trackEvent:event2 + withProperties:properties + forTransmissionTarget:transmissionTarget + flags:MSFlagsDefault]); + OCMReject([self.analyticsClassMock trackEvent:event3 + withProperties:properties + forTransmissionTarget:transmissionTarget2 + flags:MSFlagsDefault]); // When @@ -199,8 +497,14 @@ - (void)testTransmissionTargetEnabledState { // Then XCTAssertTrue([transmissionTarget2 isEnabled]); - OCMVerify(ClassMethod([self.analyticsClassMock trackEvent:event1 withProperties:properties forTransmissionTarget:transmissionTarget])); - OCMVerify(ClassMethod([self.analyticsClassMock trackEvent:event4 withProperties:properties forTransmissionTarget:transmissionTarget2])); + OCMVerify([self.analyticsClassMock trackEvent:event1 + withTypedProperties:expectedProperties + forTransmissionTarget:transmissionTarget + flags:MSFlagsDefault]); + OCMVerify([self.analyticsClassMock trackEvent:event4 + withTypedProperties:expectedProperties + forTransmissionTarget:transmissionTarget2 + flags:MSFlagsDefault]); } - (void)testTransmissionTargetNestedEnabledState { @@ -328,109 +632,123 @@ - (void)testLongListOfSubChildren { } } -- (void)testSetAndRemoveEventProperty { +- (void)testMergingEventPropertiesWithCommonPropertiesOnly { // If - MSAnalyticsTransmissionTarget *targetMock = - [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken - parentTarget:nil - channelGroup:self.channelGroupMock]; - MSPropertyConfigurator *configurator = [[MSPropertyConfigurator alloc] initWithTransmissionTarget:targetMock]; - NSString *prop1Key = @"prop1"; - NSString *prop1Value = @"val1"; - - // When - [configurator removeEventPropertyForKey:prop1Key]; - - // Then - XCTAssertEqualObjects(configurator.eventProperties, @{}); - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" - // When - [configurator removeEventPropertyForKey:nil]; - - // Then - XCTAssertEqualObjects(configurator.eventProperties, @{}); - - // When - [configurator setEventPropertyString:nil forKey:prop1Key]; + // Common properties only. + MSAnalyticsTransmissionTarget *target = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; + NSString *eventName = @"event"; + NSString *propCommonKey = @"propCommonKey"; + NSString *propCommonValue = @"propCommonValue"; + NSString *propCommonDoubleKey = @"propCommonDoubleKey"; + double propCommonDoubleValue = 298374; + NSString *propCommonKey2 = @"sharedPropKey"; + NSDate *propCommonValue2 = [NSDate date]; - // Then - XCTAssertEqualObjects(configurator.eventProperties, @{}); + [target.propertyConfigurator setEventPropertyString:propCommonValue forKey:propCommonKey]; + [target.propertyConfigurator setEventPropertyDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; + [target.propertyConfigurator setEventPropertyDate:propCommonValue2 forKey:propCommonKey2]; + MSEventProperties *expectedProperties = [MSEventProperties new]; + [expectedProperties setString:propCommonValue forKey:propCommonKey]; + [expectedProperties setDate:propCommonValue2 forKey:propCommonKey2]; + [expectedProperties setDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; // When - [configurator setEventPropertyString:prop1Value forKey:nil]; + [target trackEvent:eventName]; // Then - XCTAssertEqualObjects(configurator.eventProperties, @{}); -#pragma clang diagnostic pop - - // When - [configurator setEventPropertyString:prop1Value forKey:prop1Key]; + OCMVerify([self.analyticsClassMock trackEvent:eventName + withTypedProperties:expectedProperties + forTransmissionTarget:target + flags:MSFlagsDefault]); +} - // Then - XCTAssertEqualObjects(configurator.eventProperties, @{prop1Key : prop1Value}); +- (void)testMergingEventPropertiesWithCommonAndTrackEventProperties { // If - NSString *prop2Key = @"prop2"; - NSString *prop2Value = @"val2"; + MSAnalyticsTransmissionTarget *target = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken + parentTarget:nil + channelGroup:self.channelGroupMock]; - // When - [configurator setEventPropertyString:prop2Value forKey:prop2Key]; + // Common properties. + NSString *eventName = @"event"; + NSString *propCommonKey = @"propCommonKey"; + NSString *propCommonValue = @"propCommonValue"; + NSString *propCommonDoubleKey = @"propCommonDoubleKey"; + double propCommonDoubleValue = 298374; + NSString *propCommonKey2 = @"sharedPropKey"; + NSDate *propCommonValue2 = [NSDate date]; + [target.propertyConfigurator setEventPropertyString:propCommonValue forKey:propCommonKey]; + [target.propertyConfigurator setEventPropertyDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; + [target.propertyConfigurator setEventPropertyDate:propCommonValue2 forKey:propCommonKey2]; - // Then - XCTAssertEqualObjects(configurator.eventProperties, (@{prop1Key : prop1Value, prop2Key : prop2Value})); + // Track event properties. + NSString *propTrackKey = @"propTrackKey"; + NSString *propTrackValue = @"propTrackValue"; + NSString *propTrackKey2 = @"sharedPropKey"; + NSString *propTrackValue2 = @"propTrackValue2"; + MSEventProperties *expectedProperties = [MSEventProperties new]; + [expectedProperties setString:propCommonValue forKey:propCommonKey]; + [expectedProperties setDate:propCommonValue2 forKey:propCommonKey2]; + [expectedProperties setDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; + [expectedProperties setString:propTrackValue forKey:propTrackKey]; + [expectedProperties setString:propTrackValue2 forKey:propTrackKey2]; // When - [configurator removeEventPropertyForKey:prop1Key]; + [target trackEvent:eventName withProperties:@{propTrackKey : propTrackValue, propTrackKey2 : propTrackValue2}]; // Then - XCTAssertEqualObjects(configurator.eventProperties, @{prop2Key : prop2Value}); + OCMVerify([self.analyticsClassMock trackEvent:eventName + withTypedProperties:expectedProperties + forTransmissionTarget:target + flags:MSFlagsDefault]); } -- (void)testMergingEventProperties { +- (void)testMergingEventPropertiesWithCommonAndTrackEventTypedProperties { // If - - // Common properties only. MSAnalyticsTransmissionTarget *target = [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:kMSTestTransmissionToken parentTarget:nil channelGroup:self.channelGroupMock]; + + // Common properties. NSString *eventName = @"event"; NSString *propCommonKey = @"propCommonKey"; NSString *propCommonValue = @"propCommonValue"; + NSString *propCommonDoubleKey = @"propCommonDoubleKey"; + double propCommonDoubleValue = 298374; NSString *propCommonKey2 = @"sharedPropKey"; - NSString *propCommonValue2 = @"propCommonValue2"; + NSDate *propCommonValue2 = [NSDate date]; [target.propertyConfigurator setEventPropertyString:propCommonValue forKey:propCommonKey]; - [target.propertyConfigurator setEventPropertyString:propCommonValue2 forKey:propCommonKey2]; + [target.propertyConfigurator setEventPropertyDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; + [target.propertyConfigurator setEventPropertyDate:propCommonValue2 forKey:propCommonKey2]; - // When - [target trackEvent:eventName]; - - // Then - id commonProperties = @{propCommonKey : propCommonValue, propCommonKey2 : propCommonValue2}; - XCTAssertEqualObjects(target.propertyConfigurator.eventProperties, commonProperties); - OCMVerify(ClassMethod([self.analyticsClassMock trackEvent:eventName withProperties:commonProperties forTransmissionTarget:target])); - - // If - - // Both common properties and track event properties. + // Track event properties. NSString *propTrackKey = @"propTrackKey"; NSString *propTrackValue = @"propTrackValue"; NSString *propTrackKey2 = @"sharedPropKey"; - NSString *propTrackValue2 = @"propTrackValue2"; + BOOL propTrackValue2 = YES; + MSEventProperties *expectedProperties = [MSEventProperties new]; + [expectedProperties setString:propCommonValue forKey:propCommonKey]; + [expectedProperties setDate:propCommonValue2 forKey:propCommonKey2]; + [expectedProperties setDouble:propCommonDoubleValue forKey:propCommonDoubleKey]; + [expectedProperties setString:propTrackValue forKey:propTrackKey]; + [expectedProperties setBool:propTrackValue2 forKey:propTrackKey2]; + MSEventProperties *trackEventProperties = [MSEventProperties new]; + [trackEventProperties setString:propTrackValue forKey:propTrackKey]; + [trackEventProperties setBool:propTrackValue2 forKey:propTrackKey2]; // When - [target trackEvent:eventName withProperties:@{propTrackKey : propTrackValue, propTrackKey2 : propTrackValue2}]; + [target trackEvent:eventName withTypedProperties:trackEventProperties]; // Then - XCTAssertEqualObjects(target.propertyConfigurator.eventProperties, commonProperties); - OCMVerify(ClassMethod([self.analyticsClassMock - trackEvent:eventName - withProperties:(@{propCommonKey : propCommonValue, propTrackKey : propTrackValue, propTrackKey2 : propTrackValue2}) - forTransmissionTarget:target])); + OCMVerify([self.analyticsClassMock trackEvent:eventName + withTypedProperties:expectedProperties + forTransmissionTarget:target + flags:MSFlagsDefault]); } - (void)testEventPropertiesCascading { @@ -462,8 +780,7 @@ - (void)testEventPropertiesCascading { // Set a new property in parent. [parent.propertyConfigurator setEventPropertyString:@"44" forKey:@"d"]; - // Just to show we still get value from parent which is inherited from grand - // parent, if we remove an override. */ + // Just to show we still get value from parent which is inherited from grand parent, if we remove an override. [parent.propertyConfigurator setEventPropertyString:@"33" forKey:@"c"]; [parent.propertyConfigurator removeEventPropertyForKey:@"c"]; @@ -481,7 +798,7 @@ - (void)testEventPropertiesCascading { // Mock channel group. __block MSEventLog *eventLog; - OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { id log = nil; [invocation getArgument:&log atIndex:2]; eventLog = (MSEventLog *)log; @@ -502,6 +819,75 @@ - (void)testEventPropertiesCascading { XCTAssertEqualObjects(((MSStringTypedProperty *)eventLog.typedProperties.properties[@"g"]).value, @"7777"); } +- (void)testEventPropertiesCascadingWithTypes { + + // If + [MSAnalytics resetSharedInstance]; + id channelUnitMock = OCMProtocolMock(@protocol(MSChannelUnitProtocol)); + OCMStub([self.channelGroupMock addChannelUnitWithConfiguration:OCMOCK_ANY]).andReturn(channelUnitMock); + [MSAppCenter sharedInstance].sdkConfigured = YES; + [[MSAnalytics sharedInstance] startWithChannelGroup:self.channelGroupMock + appSecret:@"appsecret" + transmissionTargetToken:@"token" + fromApplication:YES]; + + // Prepare target instances. + MSAnalyticsTransmissionTarget *grandParent = [MSAnalytics transmissionTargetForToken:@"grand-parent"]; + MSAnalyticsTransmissionTarget *parent = [grandParent transmissionTargetForToken:@"parent"]; + MSAnalyticsTransmissionTarget *child = [parent transmissionTargetForToken:@"child"]; + + // Set properties to grand parent. + [grandParent.propertyConfigurator setEventPropertyString:@"1" forKey:@"a"]; + [grandParent.propertyConfigurator setEventPropertyDouble:2.0 forKey:@"b"]; + [grandParent.propertyConfigurator setEventPropertyString:@"3" forKey:@"c"]; + + // Override some properties. + [parent.propertyConfigurator setEventPropertyInt64:11 forKey:@"a"]; + [parent.propertyConfigurator setEventPropertyString:@"22" forKey:@"b"]; + + // Set a new property in parent. + [parent.propertyConfigurator setEventPropertyInt64:44 forKey:@"d"]; + + // Just to show we still get value from parent which is inherited from grand parent, if we remove an override. + [parent.propertyConfigurator setEventPropertyString:@"33" forKey:@"c"]; + [parent.propertyConfigurator removeEventPropertyForKey:@"c"]; + + // Override a property. + [child.propertyConfigurator setEventPropertyBool:YES forKey:@"d"]; + + // Set new properties in child. + [child.propertyConfigurator setEventPropertyDouble:55.5 forKey:@"e"]; + [child.propertyConfigurator setEventPropertyString:@"666" forKey:@"f"]; + + // Track event in child. Override some properties in trackEvent. + MSEventProperties *properties = [MSEventProperties new]; + [properties setDate:[NSDate dateWithTimeIntervalSince1970:6666] forKey:@"f"]; + [properties setString:@"7777" forKey:@"g"]; + + // Mock channel group. + __block MSEventLog *eventLog; + OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { + id log = nil; + [invocation getArgument:&log atIndex:2]; + eventLog = (MSEventLog *)log; + }); + + // When + [child trackEvent:@"eventName" withTypedProperties:properties]; + + // Then + XCTAssertNotNil(eventLog); + XCTAssertEqual([eventLog.typedProperties.properties count], 7); + XCTAssertEqual(((MSLongTypedProperty *)eventLog.typedProperties.properties[@"a"]).value, 11); + XCTAssertEqualObjects(((MSStringTypedProperty *)eventLog.typedProperties.properties[@"b"]).value, @"22"); + XCTAssertEqualObjects(((MSStringTypedProperty *)eventLog.typedProperties.properties[@"c"]).value, @"3"); + XCTAssertEqual(((MSBooleanTypedProperty *)eventLog.typedProperties.properties[@"d"]).value, YES); + XCTAssertEqual(((MSDoubleTypedProperty *)eventLog.typedProperties.properties[@"e"]).value, 55.5); + XCTAssertEqualObjects(((MSDateTimeTypedProperty *)eventLog.typedProperties.properties[@"f"]).value, + [NSDate dateWithTimeIntervalSince1970:6666]); + XCTAssertEqualObjects(((MSStringTypedProperty *)eventLog.typedProperties.properties[@"g"]).value, @"7777"); +} + - (void)testAppExtensionCommonSchemaPropertiesWithoutOverriding { // If @@ -544,6 +930,7 @@ - (void)testOverridingDefaultCommonSchemaProperties { // Set a log with default values. MSCommonSchemaLog *log = [MSCommonSchemaLog new]; + log.tag = child; log.ext = [MSCSExtensions new]; log.ext.appExt = [MSAppExtension new]; [log addTransmissionTargetToken:@"parent"]; @@ -572,6 +959,7 @@ - (void)testOverridingCommonSchemaProperties { // Set a log. MSCommonSchemaLog *log = [MSCommonSchemaLog new]; + log.tag = target; [log addTransmissionTargetToken:@"target"]; log.ext = [MSCSExtensions new]; log.ext.appExt = [MSAppExtension new]; @@ -603,6 +991,7 @@ - (void)testOverridingCommonSchemaPropertiesFromParent { // Set a log. MSCommonSchemaLog *log = [MSCommonSchemaLog new]; + log.tag = child; log.ext = [MSCSExtensions new]; log.ext.appExt = [MSAppExtension new]; log.ext.appExt.ver = @"0.0.1"; @@ -615,9 +1004,9 @@ - (void)testOverridingCommonSchemaPropertiesFromParent { [child.propertyConfigurator channel:nil prepareLog:log]; // Then - XCTAssertEqual(log.ext.appExt.ver, parent.propertyConfigurator.appVersion); - XCTAssertEqual(log.ext.appExt.name, parent.propertyConfigurator.appName); - XCTAssertEqual(log.ext.appExt.locale, parent.propertyConfigurator.appLocale); + XCTAssertEqualObjects(log.ext.appExt.ver, parent.propertyConfigurator.appVersion); + XCTAssertEqualObjects(log.ext.appExt.name, parent.propertyConfigurator.appName); + XCTAssertEqualObjects(log.ext.appExt.locale, parent.propertyConfigurator.appLocale); } - (void)testOverridingCommonSchemaPropertiesDoNothingWhenTargetIsDisabled { @@ -641,6 +1030,7 @@ - (void)testOverridingCommonSchemaPropertiesDoNothingWhenTargetIsDisabled { // Set a log. MSCommonSchemaLog *log = [MSCommonSchemaLog new]; + log.tag = child; log.ext = [MSCSExtensions new]; log.ext.appExt = [MSAppExtension new]; log.ext.appExt.ver = @"0.0.1"; @@ -675,6 +1065,7 @@ - (void)testOverridingCommonSchemaPropertiesDoNothingWhenTargetIsDisabled { // Reset a log. log = [MSCommonSchemaLog new]; + log.tag = child; log.ext = [MSCSExtensions new]; log.ext.appExt = [MSAppExtension new]; log.ext.appExt.ver = @"0.0.1"; @@ -696,7 +1087,6 @@ - (void)testOverridingCommonSchemaPropertiesDoNothingWhenTargetIsDisabled { - (void)testOverridingCommonSchemaPropertiesWithTwoChildrenUnderTheSameParent { // If - // Prepare target instances. MSAnalyticsTransmissionTarget *parent = [MSAnalytics transmissionTargetForToken:@"parent"]; MSAnalyticsTransmissionTarget *child1 = [parent transmissionTargetForToken:@"child1"]; MSAnalyticsTransmissionTarget *child2 = [parent transmissionTargetForToken:@"child2"]; @@ -711,60 +1101,54 @@ - (void)testOverridingCommonSchemaPropertiesWithTwoChildrenUnderTheSameParent { [child1.propertyConfigurator setAppName:@"Child1AppName"]; [child1.propertyConfigurator setAppLocale:@"fr-ca"]; - // Set log1. - MSCommonSchemaLog *log1 = [MSCommonSchemaLog new]; - log1.ext = [MSCSExtensions new]; - log1.ext.appExt = [MSAppExtension new]; - log1.ext.appExt.ver = @"0.0.1"; - log1.ext.appExt.name = @"base1AppName"; - log1.ext.appExt.locale = @"zh-cn"; - [log1 addTransmissionTargetToken:@"parent"]; - [log1 addTransmissionTargetToken:@"child1"]; - [log1 addTransmissionTargetToken:@"child2"]; + // Parent log. + MSCommonSchemaLog *parentLog = [MSCommonSchemaLog new]; + parentLog.tag = parent; + parentLog.ext = [MSCSExtensions new]; + parentLog.ext.appExt = [MSAppExtension new]; + parentLog.ext.appExt.ver = @"0.0.1"; + parentLog.ext.appExt.name = @"base1AppName"; + parentLog.ext.appExt.locale = @"zh-cn"; + [parentLog addTransmissionTargetToken:@"parent"]; + + // Child1 log. + MSCommonSchemaLog *child1Log = [MSCommonSchemaLog new]; + child1Log.tag = child1; + child1Log.ext = [MSCSExtensions new]; + child1Log.ext.appExt = [MSAppExtension new]; + child1Log.ext.appExt.ver = @"0.0.1"; + child1Log.ext.appExt.name = @"base1AppName"; + child1Log.ext.appExt.locale = @"zh-cn"; + [child1Log addTransmissionTargetToken:@"child1"]; + + // Child2 log. + MSCommonSchemaLog *child2Log = [MSCommonSchemaLog new]; + child2Log.tag = child2; + child2Log.ext = [MSCSExtensions new]; + child2Log.ext.appExt = [MSAppExtension new]; + child2Log.ext.appExt.ver = @"0.0.2"; + child2Log.ext.appExt.name = @"base2AppName"; + child2Log.ext.appExt.locale = @"en-us"; + [child2Log addTransmissionTargetToken:@"child2"]; // When - [parent.propertyConfigurator channel:nil prepareLog:log1]; + [parent.propertyConfigurator channel:nil prepareLog:parentLog]; + [child1.propertyConfigurator channel:nil prepareLog:child1Log]; + [child2.propertyConfigurator channel:nil prepareLog:child2Log]; // Then - XCTAssertEqual(log1.ext.appExt.ver, parent.propertyConfigurator.appVersion); - XCTAssertEqual(log1.ext.appExt.name, parent.propertyConfigurator.appName); - XCTAssertEqual(log1.ext.appExt.locale, parent.propertyConfigurator.appLocale); - - // When - [child1.propertyConfigurator channel:nil prepareLog:log1]; - - // Then - XCTAssertEqual(log1.ext.appExt.ver, child1.propertyConfigurator.appVersion); - XCTAssertEqual(log1.ext.appExt.name, child1.propertyConfigurator.appName); - XCTAssertEqual(log1.ext.appExt.locale, child1.propertyConfigurator.appLocale); - XCTAssertNotEqual(log1.ext.appExt.ver, parent.propertyConfigurator.appVersion); - XCTAssertNotEqual(log1.ext.appExt.name, parent.propertyConfigurator.appName); - XCTAssertNotEqual(log1.ext.appExt.locale, parent.propertyConfigurator.appLocale); + XCTAssertEqualObjects(parentLog.ext.appExt.ver, parent.propertyConfigurator.appVersion); + XCTAssertEqualObjects(parentLog.ext.appExt.name, parent.propertyConfigurator.appName); + XCTAssertEqualObjects(parentLog.ext.appExt.locale, parent.propertyConfigurator.appLocale); + XCTAssertEqualObjects(child1Log.ext.appExt.ver, child1.propertyConfigurator.appVersion); + XCTAssertEqualObjects(child1Log.ext.appExt.name, child1.propertyConfigurator.appName); + XCTAssertEqualObjects(child1Log.ext.appExt.locale, child1.propertyConfigurator.appLocale); + XCTAssertEqualObjects(child2Log.ext.appExt.ver, parent.propertyConfigurator.appVersion); + XCTAssertEqualObjects(child2Log.ext.appExt.name, parent.propertyConfigurator.appName); + XCTAssertEqualObjects(child2Log.ext.appExt.locale, parent.propertyConfigurator.appLocale); XCTAssertNil(child2.propertyConfigurator.appVersion); XCTAssertNil(child2.propertyConfigurator.appName); XCTAssertNil(child2.propertyConfigurator.appLocale); - - // If - MSCommonSchemaLog *log2 = [MSCommonSchemaLog new]; - log2.ext = [MSCSExtensions new]; - log2.ext.appExt = [MSAppExtension new]; - log2.ext.appExt.ver = @"0.0.2"; - log2.ext.appExt.name = @"base2AppName"; - log2.ext.appExt.locale = @"en-us"; - [log2 addTransmissionTargetToken:@"parent"]; - [log2 addTransmissionTargetToken:@"child1"]; - [log2 addTransmissionTargetToken:@"child2"]; - - // When - [child2.propertyConfigurator channel:nil prepareLog:log2]; - - // Then - XCTAssertEqual(log2.ext.appExt.ver, parent.propertyConfigurator.appVersion); - XCTAssertEqual(log2.ext.appExt.name, parent.propertyConfigurator.appName); - XCTAssertEqual(log2.ext.appExt.locale, parent.propertyConfigurator.appLocale); - XCTAssertNotEqual(log2.ext.appExt.ver, child1.propertyConfigurator.appVersion); - XCTAssertNotEqual(log2.ext.appExt.name, child1.propertyConfigurator.appName); - XCTAssertNotEqual(log2.ext.appExt.locale, child1.propertyConfigurator.appLocale); } - (void)testAddAuthenticationProvider { @@ -804,37 +1188,35 @@ - (void)testAddAuthenticationProvider { - (void)testPauseSucceedsWhenTargetIsEnabled { // If - id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); MSAnalyticsTransmissionTarget *sut = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; // When [sut pause]; // Then - OCMVerify([analyticsMock pauseTransmissionTargetForToken:kMSTestTransmissionToken]); + OCMVerify([self.analyticsClassMock pauseTransmissionTargetForToken:kMSTestTransmissionToken]); } - (void)testResumeSucceedsWhenTargetIsEnabled { // If - id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); MSAnalyticsTransmissionTarget *sut = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; // When [sut resume]; // Then - OCMVerify([analyticsMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); + OCMVerify([self.analyticsClassMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); } - (void)testPauseDoesNotPauseWhenTargetIsDisabled { // If - id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); MSAnalyticsTransmissionTarget *sut = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; + OCMStub([self.analyticsClassMock canBeUsed]).andReturn(YES); // Then - OCMReject([analyticsMock pauseTransmissionTargetForToken:kMSTestTransmissionToken]); + OCMReject([self.analyticsClassMock pauseTransmissionTargetForToken:kMSTestTransmissionToken]); // When [MSAnalytics setEnabled:NO]; @@ -844,12 +1226,11 @@ - (void)testPauseDoesNotPauseWhenTargetIsDisabled { - (void)testResumeDoesNotResumeWhenTargetIsDisabled { // If - id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); - OCMStub([analyticsMock canBeUsed]).andReturn(YES); MSAnalyticsTransmissionTarget *sut = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; + OCMStub([self.analyticsClassMock canBeUsed]).andReturn(YES); // Then - OCMReject([analyticsMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); + OCMReject([self.analyticsClassMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); // When [sut setEnabled:NO]; @@ -859,8 +1240,6 @@ - (void)testResumeDoesNotResumeWhenTargetIsDisabled { - (void)testPausedAndDisabledTargetIsResumedWhenEnabled { // If - id analyticsMock = OCMPartialMock([MSAnalytics sharedInstance]); - OCMStub([analyticsMock canBeUsed]).andReturn(YES); MSAnalyticsTransmissionTarget *sut = [MSAnalytics transmissionTargetForToken:kMSTestTransmissionToken]; [sut pause]; [sut setEnabled:NO]; @@ -869,7 +1248,7 @@ - (void)testPausedAndDisabledTargetIsResumedWhenEnabled { [sut setEnabled:YES]; // Then - OCMVerify([analyticsMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); + OCMVerify([self.analyticsClassMock resumeTransmissionTargetForToken:kMSTestTransmissionToken]); } @end diff --git a/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventLogTests.m b/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventLogTests.m index aec084ed15..74f23047ce 100644 --- a/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventLogTests.m +++ b/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventLogTests.m @@ -1,17 +1,19 @@ #import "MSAbstractLogInternal.h" -#import "MSAbstractLogPrivate.h" +#import "MSAnalyticsConstants.h" #import "MSAppExtension.h" #import "MSCSData.h" #import "MSCSExtensions.h" #import "MSCSModelConstants.h" #import "MSDeviceInternal.h" #import "MSEventLogPrivate.h" +#import "MSEventPropertiesInternal.h" #import "MSLocExtension.h" -#import "MSLogWithProperties.h" +#import "MSMetadataExtension.h" #import "MSNetExtension.h" #import "MSOSExtension.h" #import "MSProtocolExtension.h" #import "MSSDKExtension.h" +#import "MSStringTypedProperty.h" #import "MSTestFrameworks.h" #import "MSUtility+Date.h" @@ -44,7 +46,7 @@ - (void)testSerializingEventToDictionaryWorks { NSString *eventName = @"eventName"; MSDevice *device = [MSDevice new]; NSString *sessionId = @"1234567890"; - NSDictionary *properties = @{ @"Key" : @"Value" }; + NSDictionary *properties = @{@"Key" : @"Value"}; NSDate *timestamp = [NSDate date]; self.sut.eventId = eventId; @@ -78,7 +80,7 @@ - (void)testNSCodingSerializationAndDeserializationWorks { MSDevice *device = [MSDevice new]; NSString *sessionId = @"1234567890"; NSDate *timestamp = [NSDate date]; - NSDictionary *properties = @{ @"Key" : @"Value" }; + NSDictionary *properties = @{@"Key" : @"Value"}; self.sut.eventId = eventId; self.sut.name = eventName; @@ -135,89 +137,394 @@ - (void)testIsNotEqualToNil { XCTAssertFalse([self.sut isEqual:nil]); } -- (void)testConvertACPropertiesToCSproperties { +- (void)testConvertACPropertiesToCSPropertiesWhenACPropertiesNil { // If - NSDictionary *acProperties = nil; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; // When - NSDictionary *csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertNil(csProperties); + XCTAssertNil(csLog.data.properties); + XCTAssertNil(csLog.ext.metadataExt.metadata); +} + +- (void)testConvertDateTimePropertyToCSProperty { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + NSDate *date = [NSDate dateWithTimeIntervalSince1970:10000]; + [acProperties setDate:date forKey:@"time"]; + self.sut.typedProperties = acProperties; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqualObjects(csLog.data.properties[@"time"], [MSUtility dateToISO8601:date]); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata[kMSFieldDelimiter][@"time"], @(kMSDateTimeMetadataTypeId)); +} + +- (void)testConvertLongPropertyToCSProperty { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + int64_t largeNumber = 1234567890; + [acProperties setInt64:largeNumber forKey:@"largeNumber"]; + self.sut.typedProperties = acProperties; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqualObjects(csLog.data.properties[@"largeNumber"], @(largeNumber)); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata[kMSFieldDelimiter][@"largeNumber"], @(kMSLongMetadataTypeId)); +} + +- (void)testConvertDoublePropertyToCSProperty { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + double pi = 3.1415926; + [acProperties setDouble:pi forKey:@"pi"]; + self.sut.typedProperties = acProperties; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqualObjects(csLog.data.properties[@"pi"], @(pi)); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata[kMSFieldDelimiter][@"pi"], @(kMSDoubleMetadataTypeId)); +} + +- (void)testConvertStringPropertyToCSProperty { // If - acProperties = @{ @"key" : @"value", @"key2" : @"value2" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + NSString *stringValue = @"hello"; + [acProperties setString:stringValue forKey:@"text"]; + self.sut.typedProperties = acProperties; // When - csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqualObjects(csProperties, acProperties); + XCTAssertEqualObjects(csLog.data.properties[@"text"], stringValue); + XCTAssertNil(csLog.ext.metadataExt.metadata); +} + +- (void)testConvertBooleanPropertyToCSProperty { // If - acProperties = @{ @"nes.t.ed" : @"buriedValue" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + BOOL boolValue = YES; + [acProperties setBool:boolValue forKey:@"BoolKey"]; + self.sut.typedProperties = acProperties; // When - csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqualObjects(csProperties, @{ @"nes" : @{@"t" : @{@"ed" : @"buriedValue"}} }); + XCTAssertEqualObjects(csLog.data.properties[@"BoolKey"], @(boolValue)); + XCTAssertNil(csLog.ext.metadataExt.metadata); +} + +- (void)testConvertACPropertiesToCSPropertiesWhenPropertiesAreNotNested { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setString:@"value" forKey:@"key"]; + [acProperties setString:@"value2" forKey:@"key2"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedProperties = @{@"key" : @"value", @"key2" : @"value2"}; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); +} + +- (void)testConvertACPropertiesToCSPropertiesWhenPropertiesAreNested { // If - acProperties = @{ @"key" : @"value", @"nes.a" : @"1", @"nes.t.ed" : @"2", @"nes.t.ed2" : @"3", @"key2" : @"value2" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setInt64:1 forKey:@"p.a"]; + [acProperties setDouble:2.0 forKey:@"p.b"]; + [acProperties setBool:YES forKey:@"p.c"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedProperties = @{@"p" : @{@"a" : @1, @"b" : @2.0, @"c" : @YES}}; + NSDictionary *expectedMetadata = @{@"f" : @{@"p" : @{@"f" : @{@"a" : @(kMSLongMetadataTypeId), @"b" : @(kMSDoubleMetadataTypeId)}}}}; // When - csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; - NSDictionary *test = @{ @"key" : @"value", @"nes" : @{@"a" : @"1", @"t" : @{@"ed" : @"2", @"ed2" : @"3"}}, @"key2" : @"value2" }; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqualObjects(csProperties, test); + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, expectedMetadata); } -- (void)testOverrideValueToObjectProperties { +- (void)testConvertACPropertiesToCSPropertiesWhenPropertiesAreNestedWithSiblings { // If - NSDictionary *acProperties = @{ @"a.b" : @"1", @"a.b.c.d" : @"2" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setString:@"value" forKey:@"key"]; + [acProperties setString:@"1" forKey:@"nes.a"]; + [acProperties setString:@"2" forKey:@"nes.t.ed"]; + [acProperties setString:@"3" forKey:@"nes.t.ed2"]; + [acProperties setString:@"value2" forKey:@"key2"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedResult = @{@"key" : @"value", @"nes" : @{@"a" : @"1", @"t" : @{@"ed" : @"2", @"ed2" : @"3"}}, @"key2" : @"value2"}; // When - NSDictionary *csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; - NSDictionary *test1 = @{ @"a" : @{@"b" : @"1"} }; - NSDictionary *test2 = @{ @"a" : @{@"b" : @{@"c" : @{@"d" : @"2"}}} }; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqual([csProperties count], 1); - XCTAssertTrue([csProperties isEqualToDictionary:test1] || [csProperties isEqualToDictionary:test2]); + XCTAssertEqualObjects(csLog.data.properties, expectedResult); } -- (void)testOverrideObjectToValueProperties { +- (void)testPropertiesAreNotNestedWhenAtTheSameDepth { // If - NSDictionary *acProperties = @{ @"a.b.c.d" : @"1", @"a.b" : @"2" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setInt64:1 forKey:@"a.b"]; + [acProperties setDouble:2.2 forKey:@"b.c"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedProperties = @{@"a" : @{@"b" : @1}, @"b" : @{@"c" : @2.2}}; + NSDictionary *expectedMetadata = + @{@"f" : @{@"a" : @{@"f" : @{@"b" : @(kMSLongMetadataTypeId)}}, @"b" : @{@"f" : @{@"c" : @(kMSDoubleMetadataTypeId)}}}}; // When - NSDictionary *csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; - NSDictionary *test1 = @{ @"a" : @{@"b" : @{@"c" : @{@"d" : @"1"}}} }; - NSDictionary *test2 = @{ @"a" : @{@"b" : @"2"} }; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqual([csProperties count], 1); - XCTAssertTrue([csProperties isEqualToDictionary:test1] || [csProperties isEqualToDictionary:test2]); + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, expectedMetadata); +} + +- (void)testMetadataDoesNotCreateLevelsForPropertyWhenPropertyIsString { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setInt64:1 forKey:@"a.b"]; + [acProperties setString:@"2.2" forKey:@"b.c"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedProperties = @{@"a" : @{@"b" : @1}, @"b" : @{@"c" : @"2.2"}}; + NSDictionary *expectedMetadata = @{ + @"f" : @{ + @"a" : @{@"f" : @{@"b" : @(kMSLongMetadataTypeId)}}, + } + }; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, expectedMetadata); +} + +- (void)testOverridePropertiesWhenStringPropertyIsDeeper { + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + + // set "a.b" property first, "a.b.c.d" property later. + [acProperties setDouble:1.4 forKey:@"a.b"]; + [acProperties setString:@"hello" forKey:@"a.b.c.d"]; + self.sut.typedProperties = acProperties; + [self setupAndAssert:csLog]; + + // reset + csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + acProperties = [MSEventProperties new]; + + // set "a.b.c.d" property first, "a.b" property later. + [acProperties setString:@"hello" forKey:@"a.b.c.d"]; + [acProperties setDouble:1.4 forKey:@"a.b"]; + self.sut.typedProperties = acProperties; + [self setupAndAssert:csLog]; +} + +- (void)setupAndAssert:(MSCommonSchemaLog *)csLog { + + // If + NSDictionary *possibleProperties1 = @{@"a" : @{@"b" : @1.4}}; + NSDictionary *possibleProperties2 = @{@"a" : @{@"b" : @{@"c" : @{@"d" : @"hello"}}}}; + NSDictionary *possibleMetadata1 = @{@"f" : @{@"a" : @{@"f" : @{@"b" : @(kMSDoubleMetadataTypeId)}}}}; + NSDictionary *possibleMetadata2 = nil; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqual([csLog.data.properties count], 1); + + // Since there is no guarantee which property will overwrite the other, test both possibilities. + if ([csLog.data.properties isEqualToDictionary:possibleProperties1]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata1); + } else if ([csLog.data.properties isEqualToDictionary:possibleProperties2]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata2); + } else { + XCTFail(@"csLog.data.properties did not equal either expectation"); + } +} + +- (void)testOverridePropertiesWhenLongPropertyIsDeeper { + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + + // set "a.b" property first, "a.b.c.d" property later. + [acProperties setString:@"hello" forKey:@"a.b"]; + [acProperties setInt64:249 forKey:@"a.b.c.d"]; + self.sut.typedProperties = acProperties; + [self setupPropertiesAndAssert:csLog]; + + // reset + csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + acProperties = [MSEventProperties new]; + + // set "a.b.c.d" property first, "a.b" property later. + [acProperties setInt64:249 forKey:@"a.b.c.d"]; + [acProperties setString:@"hello" forKey:@"a.b"]; + self.sut.typedProperties = acProperties; + [self setupPropertiesAndAssert:csLog]; +} + +- (void)setupPropertiesAndAssert:(MSCommonSchemaLog *)csLog { + + // If + NSDictionary *possibleProperties1 = @{@"a" : @{@"b" : @"hello"}}; + NSDictionary *possibleProperties2 = @{@"a" : @{@"b" : @{@"c" : @{@"d" : @249}}}}; + NSDictionary *possibleMetadata1 = nil; + NSDictionary *possibleMetadata2 = + @{@"f" : @{@"a" : @{@"f" : @{@"b" : @{@"f" : @{@"c" : @{@"f" : @{@"d" : @(kMSLongMetadataTypeId)}}}}}}}}; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqual([csLog.data.properties count], 1); + + // Since there is no guarantee which property will overwrite the other, test both possibilities. + if ([csLog.data.properties isEqualToDictionary:possibleProperties1]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata1); + } else if ([csLog.data.properties isEqualToDictionary:possibleProperties2]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata2); + } else { + XCTFail(@"csLog.data.properties did not equal either expectation"); + } } - (void)testOverrideValueToValueProperties { // If - NSDictionary *acProperties = @{ @"a.b" : @"1", @"a.b" : @"2" }; + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setInt64:123 forKey:@"a.b"]; + [acProperties setDouble:2.43 forKey:@"a.b"]; + self.sut.typedProperties = acProperties; + NSDictionary *possibleProperties1 = @{@"a" : @{@"b" : @123}}; + NSDictionary *possibleProperties2 = @{@"a" : @{@"b" : @2.43}}; + NSDictionary *possibleMetadata1 = @{@"f" : @{@"a" : @{@"f" : @{@"b" : @(kMSLongMetadataTypeId)}}}}; + NSDictionary *possibleMetadata2 = @{@"f" : @{@"a" : @{@"f" : @{@"b" : @(kMSDoubleMetadataTypeId)}}}}; // When - NSDictionary *csProperties = [self.sut convertACPropertiesToCSproperties:acProperties]; - NSDictionary *test1 = @{ @"a" : @{@"b" : @"1"} }; - NSDictionary *test2 = @{ @"a" : @{@"b" : @"2"} }; + [self.sut setPropertiesAndMetadataForCSLog:csLog]; // Then - XCTAssertEqual([csProperties count], 1); - XCTAssertTrue([csProperties isEqualToDictionary:test1] || [csProperties isEqualToDictionary:test2]); + XCTAssertEqual([csLog.data.properties count], 1); + + // Since there is no guarantee which property will overwrite the other, test both possibilities. + if ([csLog.data.properties isEqualToDictionary:possibleProperties1]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata1); + } else if ([csLog.data.properties isEqualToDictionary:possibleProperties2]) { + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, possibleMetadata2); + } else { + XCTFail(@"csLog.data.properties did not equal either expectation"); + } +} + +- (void)testConversionWhenSiblingsHaveDifferentTypesAndOnlyOneNeedsMetadataAndTheyAreOneLevelDeep { + + // If + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.data = [MSCSData new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.metadataExt = [MSMetadataExtension new]; + MSEventProperties *acProperties = [MSEventProperties new]; + [acProperties setString:@"hello" forKey:@"a.b"]; + [acProperties setInt64:123 forKey:@"a.c"]; + [acProperties setString:@"hello" forKey:@"a.e"]; + self.sut.typedProperties = acProperties; + NSDictionary *expectedProperties = @{@"a" : @{@"c" : @123, @"b" : @"hello", @"e" : @"hello"}}; + NSDictionary *expectedMetadata = @{@"f" : @{@"a" : @{@"f" : @{@"c" : @(kMSLongMetadataTypeId)}}}}; + + // When + [self.sut setPropertiesAndMetadataForCSLog:csLog]; + + // Then + XCTAssertEqual([csLog.data.properties count], 1); + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, expectedMetadata); } - (void)testToCommonSchemaLogForTargetToken { @@ -225,7 +532,22 @@ - (void)testToCommonSchemaLogForTargetToken { // If NSString *targetToken = @"aTarget-Token"; NSString *name = @"SolarEclipse"; - NSDictionary *properties = @{ @"StartedAt" : @"11:00", @"VisibleFrom" : @"Redmond" }; + MSEventProperties *eventProperties = [MSEventProperties new]; + [eventProperties setString:@"hello" forKey:@"aStringValue"]; + [eventProperties setInt64:1234567890l forKey:@"aLongValue"]; + [eventProperties setDouble:3.14 forKey:@"aDoubleValue"]; + NSDate *date = [NSDate dateWithTimeIntervalSince1970:10000]; + NSString *dateString = [MSUtility dateToISO8601:date]; + [eventProperties setDate:date forKey:@"aDateTimeValue"]; + [eventProperties setBool:YES forKey:@"aBooleanValue"]; + NSDictionary *expectedProperties = @{ + @"aStringValue" : @"hello", + @"aLongValue" : @1234567890, + @"aDoubleValue" : @3.14, + @"aDateTimeValue" : dateString, + @"aBooleanValue" : @YES + }; + NSDictionary *expectedMetadata = @{@"f" : @{@"aLongValue" : @4, @"aDoubleValue" : @6, @"aDateTimeValue" : @9}}; NSDate *timestamp = [NSDate date]; MSDevice *device = [MSDevice new]; NSString *oemName = @"Peach"; @@ -253,10 +575,11 @@ - (void)testToCommonSchemaLogForTargetToken { self.sut.device = device; self.sut.timestamp = timestamp; self.sut.name = name; - self.sut.properties = properties; + self.sut.typedProperties = eventProperties; + self.sut.tag = [NSObject new]; // When - MSCommonSchemaLog *csLog = [self.sut toCommonSchemaLogForTargetToken:targetToken]; + MSCommonSchemaLog *csLog = [self.sut toCommonSchemaLogForTargetToken:targetToken flags:MSFlagsDefault]; // Then XCTAssertEqualObjects(csLog.ver, kMSCSVerValue); @@ -273,7 +596,9 @@ - (void)testToCommonSchemaLogForTargetToken { XCTAssertEqualObjects(csLog.ext.netExt.provider, carrierName); XCTAssertEqualObjects(csLog.ext.sdkExt.libVer, @"appcenter.ios-1.0.0"); XCTAssertEqualObjects(csLog.ext.locExt.tz, @"-07:00"); - XCTAssertEqualObjects(csLog.data.properties, properties); + XCTAssertEqualObjects(csLog.data.properties, expectedProperties); + XCTAssertEqualObjects(csLog.ext.metadataExt.metadata, expectedMetadata); + XCTAssertEqualObjects(csLog.tag, self.sut.tag); } @end diff --git a/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventPropertiesTests.m b/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventPropertiesTests.m index 255c124a59..216591ef58 100644 --- a/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventPropertiesTests.m +++ b/AppCenterAnalytics/AppCenterAnalyticsTests/MSEventPropertiesTests.m @@ -17,7 +17,7 @@ @implementation MSEventPropertiesTests - (void)testInitWithStringDictionaryWhenStringDictionaryHasValues { // If - NSDictionary *stringProperties = @{ @"key1":@"val1", @"key2":@"val2" }; + NSDictionary *stringProperties = @{ @"key1" : @"val1", @"key2" : @"val2" }; // When MSEventProperties *sut = [[MSEventProperties alloc] initWithStringDictionary:stringProperties]; @@ -42,7 +42,7 @@ - (void)testSetBoolForKey { [sut setBool:value forKey:key]; // Then - MSBooleanTypedProperty *property = (MSBooleanTypedProperty*)sut.properties[key]; + MSBooleanTypedProperty *property = (MSBooleanTypedProperty *)sut.properties[key]; XCTAssertEqual(property.name, key); XCTAssertEqual(property.value, value); } @@ -58,7 +58,7 @@ - (void)testSetInt64ForKey { [sut setInt64:value forKey:key]; // Then - MSLongTypedProperty *property = (MSLongTypedProperty*)sut.properties[key]; + MSLongTypedProperty *property = (MSLongTypedProperty *)sut.properties[key]; XCTAssertEqual(property.name, key); XCTAssertEqual(property.value, value); } @@ -74,7 +74,7 @@ - (void)testSetDoubleForKey { [sut setDouble:value forKey:key]; // Then - MSDoubleTypedProperty *property = (MSDoubleTypedProperty*)sut.properties[key]; + MSDoubleTypedProperty *property = (MSDoubleTypedProperty *)sut.properties[key]; XCTAssertEqual(property.name, key); XCTAssertEqual(property.value, value); } @@ -89,6 +89,12 @@ - (void)testSetDoubleForKeyWhenValueIsInfinity { // Then XCTAssertEqual([sut.properties count], 0); + + // When + [sut setDouble:-INFINITY forKey:@"key"]; + + // Then + XCTAssertEqual([sut.properties count], 0); } - (void)testSetDoubleForKeyWhenValueIsNaN { @@ -114,7 +120,7 @@ - (void)testSetStringForKey { [sut setString:value forKey:key]; // Then - MSStringTypedProperty *property = (MSStringTypedProperty*)sut.properties[key]; + MSStringTypedProperty *property = (MSStringTypedProperty *)sut.properties[key]; XCTAssertEqual(property.name, key); XCTAssertEqual(property.value, value); } @@ -130,7 +136,7 @@ - (void)testSetDateForKey { [sut setDate:value forKey:key]; // Then - MSDateTimeTypedProperty *property = (MSDateTimeTypedProperty*)sut.properties[key]; + MSDateTimeTypedProperty *property = (MSDateTimeTypedProperty *)sut.properties[key]; XCTAssertEqual(property.name, key); XCTAssertEqual(property.value, value); } diff --git a/AppCenterAnalytics/AppCenterAnalyticsTests/MSPropertyConfiguratorTests.m b/AppCenterAnalytics/AppCenterAnalyticsTests/MSPropertyConfiguratorTests.m index 21c47f0f9a..e29a71e17c 100644 --- a/AppCenterAnalytics/AppCenterAnalyticsTests/MSPropertyConfiguratorTests.m +++ b/AppCenterAnalytics/AppCenterAnalyticsTests/MSPropertyConfiguratorTests.m @@ -1,9 +1,16 @@ #import "MSAnalyticsTransmissionTargetInternal.h" +#import "MSAppExtension.h" +#import "MSBooleanTypedProperty.h" +#import "MSCSExtensions.h" #import "MSChannelGroupProtocol.h" #import "MSCommonSchemaLog.h" -#import "MSCSExtensions.h" +#import "MSDateTimeTypedProperty.h" #import "MSDeviceExtension.h" +#import "MSDoubleTypedProperty.h" +#import "MSLongTypedProperty.h" +#import "MSPropertyConfiguratorInternal.h" #import "MSPropertyConfiguratorPrivate.h" +#import "MSStringTypedProperty.h" #import "MSTestFrameworks.h" @interface MSPropertyConfiguratorTests : XCTestCase @@ -11,7 +18,7 @@ @interface MSPropertyConfiguratorTests : XCTestCase @property(nonatomic) MSPropertyConfigurator *sut; @property(nonatomic) MSAnalyticsTransmissionTarget *transmissionTarget; @property(nonatomic) MSAnalyticsTransmissionTarget *parentTarget; -@property(nonatomic) id configuratorClassMock; +@property(nonatomic) NSString *targetToken; @end @@ -19,37 +26,22 @@ @implementation MSPropertyConfiguratorTests - (void)setUp { [super setUp]; - self.sut = [MSPropertyConfigurator new]; - - // Mock the init so that self.sut can be injected into the target. - self.configuratorClassMock = OCMClassMock([MSPropertyConfigurator class]); - OCMStub([self.configuratorClassMock alloc]).andReturn(self.configuratorClassMock); id channelGroupMock = OCMProtocolMock(@protocol(MSChannelGroupProtocol)); - - /* - * Need to stub this twice with OCMOCK_ANY, because passing self.parentTarget won't work until the targets have been initialized, but the - * stub must be invoked inside the "init" method. - */ - OCMStub([self.configuratorClassMock initWithTransmissionTarget:OCMOCK_ANY]).andReturn([MSPropertyConfigurator new]); - self.parentTarget = OCMPartialMock( - [[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:@"456" parentTarget:nil channelGroup:channelGroupMock]); - - // Need to reset the class mock. - [self.configuratorClassMock stopMocking]; - self.configuratorClassMock = OCMClassMock([MSPropertyConfigurator class]); - OCMStub([self.configuratorClassMock alloc]).andReturn(self.configuratorClassMock); - OCMStub([self.configuratorClassMock initWithTransmissionTarget:OCMOCK_ANY]).andReturn(self.sut); - self.transmissionTarget = OCMPartialMock([[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:@"123" + self.targetToken = @"123"; + self.parentTarget = OCMPartialMock([[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:@"456" + parentTarget:nil + channelGroup:channelGroupMock]); + self.transmissionTarget = OCMPartialMock([[MSAnalyticsTransmissionTarget alloc] initWithTransmissionTargetToken:self.targetToken parentTarget:self.parentTarget channelGroup:channelGroupMock]); OCMStub([self.transmissionTarget isEnabled]).andReturn(YES); - self.sut.transmissionTarget = self.transmissionTarget; + self.sut = [[MSPropertyConfigurator alloc] initWithTransmissionTarget:self.transmissionTarget]; + OCMStub(self.transmissionTarget.propertyConfigurator).andReturn(self.sut); } - (void)tearDown { [super tearDown]; self.sut = nil; - [self.configuratorClassMock stopMocking]; } - (void)testInitializationWorks { @@ -72,6 +64,7 @@ - (void)testCollectsDeviceIdWhenShouldCollectDeviceIdIsTrue { extensions.deviceExt = OCMPartialMock([MSDeviceExtension new]); MSCommonSchemaLog *mockLog = OCMPartialMock([MSCommonSchemaLog new]); mockLog.ext = extensions; + mockLog.tag = self.transmissionTarget; [mockLog addTransmissionTargetToken:self.transmissionTarget.transmissionTargetToken]; // When @@ -102,4 +95,125 @@ - (void)testDeviceIdDoesNotPropagate { XCTAssertNil(self.sut.deviceId); } +- (void)testRemoveNonExistingEventProperty { + + // When + [self.sut removeEventPropertyForKey:@"APropKey"]; + + // Then + XCTAssertTrue([self.sut.eventProperties isEmpty]); +} + +- (void)testSetAndRemoveEventPropertiesWithNilKeys { + +// When +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + [self.sut removeEventPropertyForKey:nil]; + + // Then + XCTAssertTrue([self.sut.eventProperties isEmpty]); + + // When + [self.sut setEventPropertyString:@"val1" forKey:nil]; + [self.sut setEventPropertyDouble:234 forKey:nil]; + [self.sut setEventPropertyInt64:23 forKey:nil]; + [self.sut setEventPropertyBool:YES forKey:nil]; + [self.sut setEventPropertyDate:[NSDate new] forKey:nil]; +#pragma clang diagnostic pop + + // Then + XCTAssertTrue([self.sut.eventProperties isEmpty]); +} + +- (void)testSetEventPropertiesWithInvalidValues { + + // If + NSString *propStringKey = @"propString"; + NSString *propDateKey = @"propDate"; + NSString *propNanKey = @"propNan"; + NSString *propInfinityKey = @"propInfinity"; + +// When +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnonnull" + [self.sut removeEventPropertyForKey:nil]; + + // Then + XCTAssertTrue([self.sut.eventProperties isEmpty]); + + // When + [self.sut setEventPropertyString:nil forKey:propStringKey]; + [self.sut setEventPropertyDate:nil forKey:propDateKey]; +#pragma clang diagnostic pop + [self.sut setEventPropertyDouble:INFINITY forKey:propInfinityKey]; + [self.sut setEventPropertyDouble:-INFINITY forKey:propInfinityKey]; + [self.sut setEventPropertyDouble:NAN forKey:propNanKey]; + + // Then + XCTAssertTrue([self.sut.eventProperties isEmpty]); +} + +- (void)testSetAndRemoveEventProperty { + + // If + NSString *propStringKey = @"propString"; + NSString *propStringValue = @"val1"; + NSString *propDateKey = @"propDate"; + NSDate *propDateValue = [NSDate date]; + NSString *propDoubleKey = @"propDouble"; + double propDoubleValue = 927398.82939; + NSString *propInt64Key = @"propInt64"; + int64_t propInt64Value = 5000000000; + NSString *propBoolKey = @"propBool"; + BOOL propBoolValue = YES; + + // When + // Set properties of all types. + [self.sut setEventPropertyString:propStringValue forKey:propStringKey]; + [self.sut setEventPropertyDate:propDateValue forKey:propDateKey]; + [self.sut setEventPropertyDouble:propDoubleValue forKey:propDoubleKey]; + [self.sut setEventPropertyInt64:propInt64Value forKey:propInt64Key]; + [self.sut setEventPropertyBool:propBoolValue forKey:propBoolKey]; + + // Then + XCTAssertEqual([self.sut.eventProperties.properties count], 5); + XCTAssertEqualObjects(((MSStringTypedProperty *)(self.sut.eventProperties.properties[propStringKey])).value, propStringValue); + XCTAssertEqualObjects(((MSDateTimeTypedProperty *)(self.sut.eventProperties.properties[propDateKey])).value, propDateValue); + XCTAssertEqual(((MSDoubleTypedProperty *)(self.sut.eventProperties.properties[propDoubleKey])).value, propDoubleValue); + XCTAssertEqual(((MSLongTypedProperty *)(self.sut.eventProperties.properties[propInt64Key])).value, propInt64Value); + XCTAssertEqual(((MSBooleanTypedProperty *)(self.sut.eventProperties.properties[propBoolKey])).value, propBoolValue); + + // When + [self.sut removeEventPropertyForKey:propStringKey]; + + // Then + XCTAssertEqual([self.sut.eventProperties.properties count], 4); + XCTAssertEqualObjects(((MSDateTimeTypedProperty *)(self.sut.eventProperties.properties[propDateKey])).value, propDateValue); + XCTAssertEqual(((MSDoubleTypedProperty *)(self.sut.eventProperties.properties[propDoubleKey])).value, propDoubleValue); + XCTAssertEqual(((MSLongTypedProperty *)(self.sut.eventProperties.properties[propInt64Key])).value, propInt64Value); + XCTAssertEqual(((MSBooleanTypedProperty *)(self.sut.eventProperties.properties[propBoolKey])).value, propBoolValue); +} + +- (void)testPropertiesAreNotAppliedToLogsOfDifferentTagWithSameToken { + + // If + id channelMock = OCMProtocolMock(@protocol(MSChannelProtocol)); + MSCommonSchemaLog *csLog = [MSCommonSchemaLog new]; + csLog.ext = [MSCSExtensions new]; + csLog.ext.appExt = [MSAppExtension new]; + [csLog addTransmissionTargetToken:self.targetToken]; + [self.sut setAppLocale:@"en-US"]; + [self.sut setAppVersion:@"1.0.0"]; + [self.sut setAppName:@"tim"]; + + // When + [self.sut channel:channelMock prepareLog:csLog]; + + // Then + XCTAssertNil(csLog.ext.appExt.ver); + XCTAssertNil(csLog.ext.appExt.locale); + XCTAssertNil(csLog.ext.appExt.name); +} + @end diff --git a/AppCenterCrashes/AppCenterCrashes.xcodeproj/project.pbxproj b/AppCenterCrashes/AppCenterCrashes.xcodeproj/project.pbxproj index 7dc5475854..4aec58ab0e 100644 --- a/AppCenterCrashes/AppCenterCrashes.xcodeproj/project.pbxproj +++ b/AppCenterCrashes/AppCenterCrashes.xcodeproj/project.pbxproj @@ -336,6 +336,26 @@ 6EC99A3C1D416CCF0016C325 /* live_report_xamarin.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 6EC99A351D416CCF0016C325 /* live_report_xamarin.plcrash */; }; 6EC99A3D1D416CCF0016C325 /* log_report_xamarin in Resources */ = {isa = PBXBuildFile; fileRef = 6EC99A361D416CCF0016C325 /* log_report_xamarin */; }; 8024743B1EAE077800AEC284 /* MSErrorAttachmentLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 8024743A1EAE077800AEC284 /* MSErrorAttachmentLog.m */; }; + 80955E2B218B498700CD59A1 /* macOS_report_write_to_readonly_page.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E17218B497200CD59A1 /* macOS_report_write_to_readonly_page.plcrash */; }; + 80955E2C218B498900CD59A1 /* macOS_report_throw_cpp_exception.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1B218B497300CD59A1 /* macOS_report_throw_cpp_exception.plcrash */; }; + 80955E2D218B498D00CD59A1 /* macOS_report_swift.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E2A218B497400CD59A1 /* macOS_report_swift.plcrash */; }; + 80955E2E218B499000CD59A1 /* macOS_report_stack_overflow.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E27218B497400CD59A1 /* macOS_report_stack_overflow.plcrash */; }; + 80955E2F218B499300CD59A1 /* macOS_report_smash_the_top_of_the_stack.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E26218B497400CD59A1 /* macOS_report_smash_the_top_of_the_stack.plcrash */; }; + 80955E30218B499500CD59A1 /* macOS_report_smash_the_bottom_of_the_stack.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E23218B497300CD59A1 /* macOS_report_smash_the_bottom_of_the_stack.plcrash */; }; + 80955E31218B499800CD59A1 /* macOS_report_overwrite_link_register.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E28218B497400CD59A1 /* macOS_report_overwrite_link_register.plcrash */; }; + 80955E32218B499A00CD59A1 /* macOS_report_objc_message_released_object.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E29218B497400CD59A1 /* macOS_report_objc_message_released_object.plcrash */; }; + 80955E33218B499C00CD59A1 /* macOS_report_objc_crash_inside_msgsend.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1D218B497300CD59A1 /* macOS_report_objc_crash_inside_msgsend.plcrash */; }; + 80955E34218B499F00CD59A1 /* macOS_report_objc_access_non_object_as_object.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E18218B497200CD59A1 /* macOS_report_objc_access_non_object_as_object.plcrash */; }; + 80955E35218B49A200CD59A1 /* macOS_report_jump_into_nx_page.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1C218B497300CD59A1 /* macOS_report_jump_into_nx_page.plcrash */; }; + 80955E36218B49A400CD59A1 /* macOS_report_execute_undefined_instruction.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E25218B497400CD59A1 /* macOS_report_execute_undefined_instruction.plcrash */; }; + 80955E37218B49A600CD59A1 /* macOS_report_execute_privileged_instruction.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E19218B497300CD59A1 /* macOS_report_execute_privileged_instruction.plcrash */; }; + 80955E38218B49A900CD59A1 /* macOS_report_dwarf_unwinding.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1F218B497300CD59A1 /* macOS_report_dwarf_unwinding.plcrash */; }; + 80955E39218B49AC00CD59A1 /* macOS_report_dereference_null_pointer.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E21218B497300CD59A1 /* macOS_report_dereference_null_pointer.plcrash */; }; + 80955E3A218B49AE00CD59A1 /* macOS_report_dereference_bad_pointer.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E20218B497300CD59A1 /* macOS_report_dereference_bad_pointer.plcrash */; }; + 80955E3B218B49B100CD59A1 /* macOS_report_corrupt_objc_runtime_structure.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1A218B497300CD59A1 /* macOS_report_corrupt_objc_runtime_structure.plcrash */; }; + 80955E3C218B49B400CD59A1 /* macOS_report_corrupt_malloc_internal_info.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E24218B497400CD59A1 /* macOS_report_corrupt_malloc_internal_info.plcrash */; }; + 80955E3D218B49B600CD59A1 /* macOS_report_builtin_trap.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E1E218B497300CD59A1 /* macOS_report_builtin_trap.plcrash */; }; + 80955E3E218B49B900CD59A1 /* macOS_report_abort.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = 80955E22218B497300CD59A1 /* macOS_report_abort.plcrash */; }; 922446841F621F3A00E4034A /* MSHandledErrorLog.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446831F621F3A00E4034A /* MSHandledErrorLog.m */; }; 9224468C1F62276C00E4034A /* MSHandledErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */; }; 9224468D1F62277400E4034A /* MSHandledErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */; }; @@ -362,6 +382,9 @@ B2FF130C1DD12F61003DC677 /* MSAppleErrorLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B2FF130B1DD12F61003DC677 /* MSAppleErrorLogTests.m */; }; BA682CFA6F4C5A8841507CF7 /* MSMockCrashesDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = BA68266A68B7F21A86A093B0 /* MSMockCrashesDelegate.m */; }; C2A4A26F1EE76B970080FD22 /* libCrashReporter-MacOSX-Static.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 049327B11ECA19FD00D0187A /* libCrashReporter-MacOSX-Static.a */; }; + F82E4C75217F1FD700EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C74217F1FD600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C76217F1FD700EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C74217F1FD600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C77217F1FD700EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C74217F1FD600EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; F851DAED1E81867D00525570 /* MSCrashesCXXExceptionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F851DAEC1E81867D00525570 /* MSCrashesCXXExceptionTests.mm */; }; F859D1051E549B45008B2D8E /* live_report_bad_ptr.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = F859D0F41E549B45008B2D8E /* live_report_bad_ptr.plcrash */; }; F859D1061E549B45008B2D8E /* live_report_call_abort.plcrash in Resources */ = {isa = PBXBuildFile; fileRef = F859D0F51E549B45008B2D8E /* live_report_call_abort.plcrash */; }; @@ -572,6 +595,26 @@ 6EC99A351D416CCF0016C325 /* live_report_xamarin.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = live_report_xamarin.plcrash; sourceTree = ""; }; 6EC99A361D416CCF0016C325 /* log_report_xamarin */ = {isa = PBXFileReference; fileEncoding = 1; lastKnownFileType = text; path = log_report_xamarin; sourceTree = ""; }; 8024743A1EAE077800AEC284 /* MSErrorAttachmentLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSErrorAttachmentLog.m; sourceTree = ""; }; + 80955E17218B497200CD59A1 /* macOS_report_write_to_readonly_page.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_write_to_readonly_page.plcrash; sourceTree = ""; }; + 80955E18218B497200CD59A1 /* macOS_report_objc_access_non_object_as_object.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_objc_access_non_object_as_object.plcrash; sourceTree = ""; }; + 80955E19218B497300CD59A1 /* macOS_report_execute_privileged_instruction.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_execute_privileged_instruction.plcrash; sourceTree = ""; }; + 80955E1A218B497300CD59A1 /* macOS_report_corrupt_objc_runtime_structure.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_corrupt_objc_runtime_structure.plcrash; sourceTree = ""; }; + 80955E1B218B497300CD59A1 /* macOS_report_throw_cpp_exception.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_throw_cpp_exception.plcrash; sourceTree = ""; }; + 80955E1C218B497300CD59A1 /* macOS_report_jump_into_nx_page.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_jump_into_nx_page.plcrash; sourceTree = ""; }; + 80955E1D218B497300CD59A1 /* macOS_report_objc_crash_inside_msgsend.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_objc_crash_inside_msgsend.plcrash; sourceTree = ""; }; + 80955E1E218B497300CD59A1 /* macOS_report_builtin_trap.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_builtin_trap.plcrash; sourceTree = ""; }; + 80955E1F218B497300CD59A1 /* macOS_report_dwarf_unwinding.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_dwarf_unwinding.plcrash; sourceTree = ""; }; + 80955E20218B497300CD59A1 /* macOS_report_dereference_bad_pointer.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_dereference_bad_pointer.plcrash; sourceTree = ""; }; + 80955E21218B497300CD59A1 /* macOS_report_dereference_null_pointer.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_dereference_null_pointer.plcrash; sourceTree = ""; }; + 80955E22218B497300CD59A1 /* macOS_report_abort.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_abort.plcrash; sourceTree = ""; }; + 80955E23218B497300CD59A1 /* macOS_report_smash_the_bottom_of_the_stack.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_smash_the_bottom_of_the_stack.plcrash; sourceTree = ""; }; + 80955E24218B497400CD59A1 /* macOS_report_corrupt_malloc_internal_info.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_corrupt_malloc_internal_info.plcrash; sourceTree = ""; }; + 80955E25218B497400CD59A1 /* macOS_report_execute_undefined_instruction.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_execute_undefined_instruction.plcrash; sourceTree = ""; }; + 80955E26218B497400CD59A1 /* macOS_report_smash_the_top_of_the_stack.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_smash_the_top_of_the_stack.plcrash; sourceTree = ""; }; + 80955E27218B497400CD59A1 /* macOS_report_stack_overflow.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_stack_overflow.plcrash; sourceTree = ""; }; + 80955E28218B497400CD59A1 /* macOS_report_overwrite_link_register.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_overwrite_link_register.plcrash; sourceTree = ""; }; + 80955E29218B497400CD59A1 /* macOS_report_objc_message_released_object.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_objc_message_released_object.plcrash; sourceTree = ""; }; + 80955E2A218B497400CD59A1 /* macOS_report_swift.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = macOS_report_swift.plcrash; sourceTree = ""; }; 922446821F621D4500E4034A /* MSHandledErrorLog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSHandledErrorLog.h; sourceTree = ""; }; 922446831F621F3A00E4034A /* MSHandledErrorLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHandledErrorLog.m; sourceTree = ""; }; 922446851F6222EC00E4034A /* MSHandledErrorLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSHandledErrorLogTests.m; sourceTree = ""; }; @@ -594,6 +637,7 @@ B2FF130B1DD12F61003DC677 /* MSAppleErrorLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAppleErrorLogTests.m; sourceTree = ""; }; BA68266A68B7F21A86A093B0 /* MSMockCrashesDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSMockCrashesDelegate.m; sourceTree = ""; }; BA6829F386854E39B92F77C4 /* MSMockCrashesDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSMockCrashesDelegate.h; sourceTree = ""; }; + F82E4C74217F1FD600EDAB34 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; F851DAEC1E81867D00525570 /* MSCrashesCXXExceptionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSCrashesCXXExceptionTests.mm; sourceTree = ""; }; F859D0F41E549B45008B2D8E /* live_report_bad_ptr.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = live_report_bad_ptr.plcrash; sourceTree = ""; }; F859D0F51E549B45008B2D8E /* live_report_call_abort.plcrash */ = {isa = PBXFileReference; lastKnownFileType = file; path = live_report_call_abort.plcrash; sourceTree = ""; }; @@ -953,6 +997,7 @@ 6E171AEC1D22F7AB000DC480 /* Frameworks */ = { isa = PBXGroup; children = ( + F82E4C74217F1FD600EDAB34 /* sqlite3.c */, 0446DFA91F3B98A300C8E338 /* libAppCenter.a */, 0446DFAB1F3B98A800C8E338 /* libAppCenter.a */, 0446DFA41F3B986B00C8E338 /* libAppCenter.a */, @@ -1045,6 +1090,26 @@ B26A310A2189185B00F09AE1 /* CrashProbeMacOS */ = { isa = PBXGroup; children = ( + 80955E22218B497300CD59A1 /* macOS_report_abort.plcrash */, + 80955E1E218B497300CD59A1 /* macOS_report_builtin_trap.plcrash */, + 80955E24218B497400CD59A1 /* macOS_report_corrupt_malloc_internal_info.plcrash */, + 80955E1A218B497300CD59A1 /* macOS_report_corrupt_objc_runtime_structure.plcrash */, + 80955E20218B497300CD59A1 /* macOS_report_dereference_bad_pointer.plcrash */, + 80955E21218B497300CD59A1 /* macOS_report_dereference_null_pointer.plcrash */, + 80955E1F218B497300CD59A1 /* macOS_report_dwarf_unwinding.plcrash */, + 80955E19218B497300CD59A1 /* macOS_report_execute_privileged_instruction.plcrash */, + 80955E25218B497400CD59A1 /* macOS_report_execute_undefined_instruction.plcrash */, + 80955E1C218B497300CD59A1 /* macOS_report_jump_into_nx_page.plcrash */, + 80955E18218B497200CD59A1 /* macOS_report_objc_access_non_object_as_object.plcrash */, + 80955E1D218B497300CD59A1 /* macOS_report_objc_crash_inside_msgsend.plcrash */, + 80955E29218B497400CD59A1 /* macOS_report_objc_message_released_object.plcrash */, + 80955E28218B497400CD59A1 /* macOS_report_overwrite_link_register.plcrash */, + 80955E23218B497300CD59A1 /* macOS_report_smash_the_bottom_of_the_stack.plcrash */, + 80955E26218B497400CD59A1 /* macOS_report_smash_the_top_of_the_stack.plcrash */, + 80955E27218B497400CD59A1 /* macOS_report_stack_overflow.plcrash */, + 80955E2A218B497400CD59A1 /* macOS_report_swift.plcrash */, + 80955E1B218B497300CD59A1 /* macOS_report_throw_cpp_exception.plcrash */, + 80955E17218B497200CD59A1 /* macOS_report_write_to_readonly_page.plcrash */, B26A310B21891A7800F09AE1 /* macOS_report_pthread_lock.plcrash */, ); path = CrashProbeMacOS; @@ -1404,28 +1469,48 @@ 0493277A1ECA170D00D0187A /* live_report_objc_exception.plcrash in Resources */, 0493277B1ECA170D00D0187A /* live_report_call_abort.plcrash in Resources */, 0493277C1ECA170D00D0187A /* live_report_smash_bottom.plcrash in Resources */, + 80955E2B218B498700CD59A1 /* macOS_report_write_to_readonly_page.plcrash in Resources */, + 80955E34218B499F00CD59A1 /* macOS_report_objc_access_non_object_as_object.plcrash in Resources */, + 80955E2D218B498D00CD59A1 /* macOS_report_swift.plcrash in Resources */, 0493277D1ECA170D00D0187A /* live_report_swift_crash.plcrash in Resources */, + 80955E3E218B49B900CD59A1 /* macOS_report_abort.plcrash in Resources */, + 80955E37218B49A600CD59A1 /* macOS_report_execute_privileged_instruction.plcrash in Resources */, 0493277E1ECA170D00D0187A /* live_report_exception_marketing.plcrash in Resources */, + 80955E38218B49A900CD59A1 /* macOS_report_dwarf_unwinding.plcrash in Resources */, 0493277F1ECA170D00D0187A /* live_report_corrupt_objc.plcrash in Resources */, + 80955E3D218B49B600CD59A1 /* macOS_report_builtin_trap.plcrash in Resources */, + 80955E32218B499A00CD59A1 /* macOS_report_objc_message_released_object.plcrash in Resources */, 049327801ECA170D00D0187A /* live_report_signal.plcrash in Resources */, + 80955E2F218B499300CD59A1 /* macOS_report_smash_the_top_of_the_stack.plcrash in Resources */, + 80955E2E218B499000CD59A1 /* macOS_report_stack_overflow.plcrash in Resources */, + 80955E31218B499800CD59A1 /* macOS_report_overwrite_link_register.plcrash in Resources */, 049327811ECA170D00D0187A /* live_report_xamarin.plcrash in Resources */, 049327821ECA170D00D0187A /* live_report_objc_msgsend.plcrash in Resources */, 049327831ECA170D00D0187A /* live_report_cpp_exception.plcrash in Resources */, + 80955E3A218B49AE00CD59A1 /* macOS_report_dereference_bad_pointer.plcrash in Resources */, 049327841ECA170D00D0187A /* live_report_call_trap.plcrash in Resources */, 049327851ECA170D00D0187A /* live_report_overwrite_link.plcrash in Resources */, 049327861ECA170D00D0187A /* live_report_objc_released.plcrash in Resources */, 049327871ECA170D00D0187A /* live_report_undefined_instr.plcrash in Resources */, + 80955E30218B499500CD59A1 /* macOS_report_smash_the_bottom_of_the_stack.plcrash in Resources */, 049327881ECA170D00D0187A /* live_report_null_ptr.plcrash in Resources */, 049327891ECA170D00D0187A /* log_report_xamarin in Resources */, 0493278A1ECA170D00D0187A /* live_report_jump_into_nx.plcrash in Resources */, 0493278B1ECA170D00D0187A /* live_report_pthread_lock.plcrash in Resources */, B266B751216FECBB00C9E322 /* live_report_arm64e.plcrash in Resources */, 0493278C1ECA170D00D0187A /* live_report_bad_ptr.plcrash in Resources */, + 80955E36218B49A400CD59A1 /* macOS_report_execute_undefined_instruction.plcrash in Resources */, + 80955E33218B499C00CD59A1 /* macOS_report_objc_crash_inside_msgsend.plcrash in Resources */, 0493278D1ECA170D00D0187A /* live_report_write_readonly.plcrash in Resources */, 0493278E1ECA170D00D0187A /* live_report_smash_top.plcrash in Resources */, + 80955E39218B49AC00CD59A1 /* macOS_report_dereference_null_pointer.plcrash in Resources */, + 80955E35218B49A200CD59A1 /* macOS_report_jump_into_nx_page.plcrash in Resources */, + 80955E3C218B49B400CD59A1 /* macOS_report_corrupt_malloc_internal_info.plcrash in Resources */, + 80955E2C218B498900CD59A1 /* macOS_report_throw_cpp_exception.plcrash in Resources */, 0493278F1ECA170D00D0187A /* live_report_signal_marketing.plcrash in Resources */, 049327901ECA170D00D0187A /* live_report_empty.plcrash in Resources */, 049327911ECA170D00D0187A /* live_report_exception.plcrash in Resources */, + 80955E3B218B49B100CD59A1 /* macOS_report_corrupt_objc_runtime_structure.plcrash in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1606,6 +1691,7 @@ 0446DF6C1F3B977100C8E338 /* MSExceptionTests.m in Sources */, 0446DF6D1F3B977100C8E338 /* MSErrorReportTests.m in Sources */, 0446DF6E1F3B977100C8E338 /* MSWrapperCrashesHelperTests.m in Sources */, + F82E4C77217F1FD700EDAB34 /* sqlite3.c in Sources */, 043281671F74A677002F7205 /* MSMockUserDefaults.m in Sources */, 0446DF6F1F3B977100C8E338 /* MSStackFrameTests.m in Sources */, 0446DF701F3B977100C8E338 /* MSCrashesCXXExceptionTests.mm in Sources */, @@ -1656,6 +1742,7 @@ 049327671ECA170D00D0187A /* MSExceptionTests.m in Sources */, 049327681ECA170D00D0187A /* MSErrorReportTests.m in Sources */, 04CC3B8A1F3528E200F7D92A /* MSWrapperCrashesHelperTests.m in Sources */, + F82E4C76217F1FD700EDAB34 /* sqlite3.c in Sources */, 043281661F74A675002F7205 /* MSMockUserDefaults.m in Sources */, 049327691ECA170D00D0187A /* MSStackFrameTests.m in Sources */, 0493276A1ECA170D00D0187A /* MSCrashesCXXExceptionTests.mm in Sources */, @@ -1731,6 +1818,7 @@ 6E7D5C8A1D3EC504009EC9AC /* MSExceptionTests.m in Sources */, B2F120D71D65469D0060DED7 /* MSErrorReportTests.m in Sources */, 352B1D6F1F27C36300684A7F /* MSWrapperCrashesHelperTests.m in Sources */, + F82E4C75217F1FD700EDAB34 /* sqlite3.c in Sources */, 043281651F74A673002F7205 /* MSMockUserDefaults.m in Sources */, 6E7D5C871D3EC332009EC9AC /* MSStackFrameTests.m in Sources */, F851DAED1E81867D00525570 /* MSCrashesCXXExceptionTests.mm in Sources */, diff --git a/AppCenterCrashes/AppCenterCrashes/MSCrashes.mm b/AppCenterCrashes/AppCenterCrashes/MSCrashes.mm index 651ffe60ca..b6caa34247 100644 --- a/AppCenterCrashes/AppCenterCrashes/MSCrashes.mm +++ b/AppCenterCrashes/AppCenterCrashes/MSCrashes.mm @@ -2,12 +2,12 @@ #import "MSAppleErrorLog.h" #import "MSChannelUnitConfiguration.h" #import "MSChannelUnitProtocol.h" +#import "MSCrashHandlerSetupDelegate.h" #import "MSCrashesCXXExceptionWrapperException.h" #import "MSCrashesDelegate.h" #import "MSCrashesInternal.h" #import "MSCrashesPrivate.h" #import "MSCrashesUtil.h" -#import "MSCrashHandlerSetupDelegate.h" #import "MSEncrypter.h" #import "MSErrorAttachmentLog.h" #import "MSErrorAttachmentLogInternal.h" @@ -16,8 +16,8 @@ #import "MSServiceAbstractProtected.h" #import "MSSessionContext.h" #import "MSUtility+File.h" -#import "MSWrapperExceptionManagerInternal.h" #import "MSWrapperCrashesHelper.h" +#import "MSWrapperExceptionManagerInternal.h" /** * Service name for initialization. @@ -260,14 +260,6 @@ - (instancetype)init { pendingBatchesLimit:3]; _targetTokenEncrypter = [[MSEncrypter alloc] initWithDefaultKey]; -#if TARGET_OS_OSX - /* - * AppKit is preventing applications from crashing on macOS so PLCrashReport cannot catch any crashes. - * Setting this flag will let application crash on uncaught exceptions. - */ - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions" : @YES }]; -#endif - /* * Using our own queue with high priority as the default main queue is slower and we want the files to be created as quickly as possible * in case the app is crashing fast. @@ -417,7 +409,10 @@ - (void)setEnableMachExceptionHandler:(BOOL)enableMachExceptionHandler { * This means the Crashes module can't message any other module. All logic related to the buffer needs to happen before the crash and then, * at crash time, crashes has all info in place to save the buffer safely from the main thread (other threads are killed at crash time). */ -- (void)channel:(id)__unused channel didPrepareLog:(id)log withInternalId:(NSString *)internalId { +- (void)channel:(id)__unused channel + didPrepareLog:(id)log + internalId:(NSString *)internalId + flags:(MSFlags)__unused flags { // Don't buffer event if log is empty, crashes module is disabled or the log is related to crash. NSObject *logObject = static_cast(log); @@ -486,7 +481,7 @@ - (void)channel:(id)__unused channel didPrepareLog:(id } } -- (void)channel:(id)__unused channel didCompleteEnqueueingLog:(id)log withInternalId:(NSString *)internalId { +- (void)channel:(id)__unused channel didCompleteEnqueueingLog:(id)log internalId:(NSString *)internalId { @synchronized(self) { for (auto it = msCrashesLogBuffer.begin(), end = msCrashesLogBuffer.end(); it != end; ++it) { NSString *bufferId = [NSString stringWithCString:it->internalId.c_str() encoding:NSUTF8StringEncoding]; @@ -756,8 +751,8 @@ - (void)processLogBufferAfterCrash { pendingBatchesLimit:1]]; // Iterate over each file in it with the kMSLogBufferFileExtension and send the log if a log can be deserialized. - NSArray *files = - [MSUtility contentsOfDirectory:[NSString stringWithFormat:@"%@", self.logBufferPathComponent] propertiesForKeys:nil]; + NSArray *files = [MSUtility contentsOfDirectory:[NSString stringWithFormat:@"%@", self.logBufferPathComponent] + propertiesForKeys:nil]; for (NSURL *fileURL in files) { if ([[fileURL pathExtension] isEqualToString:kMSLogBufferFileExtension]) { NSData *serializedLog = [NSData dataWithContentsOfURL:fileURL]; @@ -766,8 +761,8 @@ - (void)processLogBufferAfterCrash { if (item) { // Try to set target token. - NSString *targetTokenFilePath = - [fileURL.path stringByReplacingOccurrencesOfString:kMSLogBufferFileExtension withString:kMSTargetTokenFileExtension]; + NSString *targetTokenFilePath = [fileURL.path stringByReplacingOccurrencesOfString:kMSLogBufferFileExtension + withString:kMSTargetTokenFileExtension]; NSURL *targetTokenFileURL = [NSURL fileURLWithPath:targetTokenFilePath]; NSString *targetToken = [NSString stringWithContentsOfURL:targetTokenFileURL encoding:NSUTF8StringEncoding error:nil]; if (targetToken != nil) { @@ -780,7 +775,8 @@ - (void)processLogBufferAfterCrash { // Buffered logs are used sending their own channel. It will never contain more than 50 logs. MSLogDebug([MSCrashes logTag], @"Re-enqueueing buffered log, type: %@.", item.type); - [self.bufferChannelUnit enqueueItem:item]; + // TODO Must read log priority and serialize to be able to enqueue with proper criticality + [self.bufferChannelUnit enqueueItem:item flags:MSFlagsDefault]; } } @@ -855,7 +851,7 @@ - (void)sendErrorAttachments:(NSArray *)errorAttachments MSLogError([MSCrashes logTag], @"Not all required fields are present in MSErrorAttachmentLog."); continue; } - [self.channelUnit enqueueItem:attachment]; + [self.channelUnit enqueueItem:attachment flags:MSFlagsDefault]; ++totalProcessedAttachments; } if (totalProcessedAttachments > kMaxAttachmentsPerCrashReport) { @@ -940,8 +936,10 @@ - (void)removeAnalyzerFile { } - (void)createAnalyzerFile { - NSURL *analyzerURL = - [MSUtility createFileAtPathComponent:self.analyzerInProgressFilePathComponent withData:nil atomically:NO forceOverwrite:NO]; + NSURL *analyzerURL = [MSUtility createFileAtPathComponent:self.analyzerInProgressFilePathComponent + withData:nil + atomically:NO + forceOverwrite:NO]; if (!analyzerURL) { MSLogError([MSCrashes logTag], @"Couldn't create crash analyzer file."); } @@ -983,8 +981,8 @@ - (void)setupLogBuffer { msCrashesLogBuffer[i] = MSCrashesBufferedLog(path, nil); // Save target token path as well to avoid memory allocation when saving. - NSString *targetTokenPath = - [path stringByReplacingOccurrencesOfString:kMSLogBufferFileExtension withString:kMSTargetTokenFileExtension]; + NSString *targetTokenPath = [path stringByReplacingOccurrencesOfString:kMSLogBufferFileExtension + withString:kMSTargetTokenFileExtension]; msCrashesLogBuffer[i].targetTokenPath = targetTokenPath.UTF8String; } } @@ -1134,7 +1132,7 @@ - (void)notifyWithUserConfirmation:(MSUserConfirmation)userConfirmation { log.sid = [[MSSessionContext sharedInstance] sessionIdAt:log.timestamp]; // Then, enqueue crash log. - [self.channelUnit enqueueItem:log]; + [self.channelUnit enqueueItem:log flags:MSFlagsPersistenceCritical]; // Send error attachments. [self sendErrorAttachments:attachments withIncidentIdentifier:report.incidentIdentifier]; @@ -1169,12 +1167,13 @@ - (void)trackModelException:(MSException *)exception withProperties:(NSDictionar if (properties && properties.count > 0) { // Send only valid properties. - log.properties = - [MSUtility validateProperties:properties forLogName:[NSString stringWithFormat:@"ErrorLog: %@", log.errorId] type:log.type]; + log.properties = [MSUtility validateProperties:properties + forLogName:[NSString stringWithFormat:@"ErrorLog: %@", log.errorId] + type:log.type]; } // Enqueue log. - [self.channelUnit enqueueItem:log]; + [self.channelUnit enqueueItem:log flags:MSFlagsDefault]; } @end diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_abort.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_abort.plcrash new file mode 100644 index 0000000000..2d3dcb9f8e Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_abort.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_builtin_trap.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_builtin_trap.plcrash new file mode 100644 index 0000000000..4428a9516d Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_builtin_trap.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_malloc_internal_info.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_malloc_internal_info.plcrash new file mode 100644 index 0000000000..a4235e1c56 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_malloc_internal_info.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_objc_runtime_structure.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_objc_runtime_structure.plcrash new file mode 100644 index 0000000000..446f700d1c Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_corrupt_objc_runtime_structure.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_bad_pointer.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_bad_pointer.plcrash new file mode 100644 index 0000000000..0e94ebdb9e Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_bad_pointer.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_null_pointer.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_null_pointer.plcrash new file mode 100644 index 0000000000..2b6bb1ca81 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dereference_null_pointer.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dwarf_unwinding.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dwarf_unwinding.plcrash new file mode 100644 index 0000000000..b494ab5d70 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_dwarf_unwinding.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_privileged_instruction.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_privileged_instruction.plcrash new file mode 100644 index 0000000000..90a53961ec Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_privileged_instruction.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_undefined_instruction.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_undefined_instruction.plcrash new file mode 100644 index 0000000000..04026538a5 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_execute_undefined_instruction.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_jump_into_nx_page.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_jump_into_nx_page.plcrash new file mode 100644 index 0000000000..bd174fa972 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_jump_into_nx_page.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_access_non_object_as_object.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_access_non_object_as_object.plcrash new file mode 100644 index 0000000000..530eb6d158 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_access_non_object_as_object.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_crash_inside_msgsend.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_crash_inside_msgsend.plcrash new file mode 100644 index 0000000000..f5a377077e Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_crash_inside_msgsend.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_message_released_object.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_message_released_object.plcrash new file mode 100644 index 0000000000..0b116cd30b Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_objc_message_released_object.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_overwrite_link_register.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_overwrite_link_register.plcrash new file mode 100644 index 0000000000..13a7e08b38 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_overwrite_link_register.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_bottom_of_the_stack.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_bottom_of_the_stack.plcrash new file mode 100644 index 0000000000..b7d07b6a56 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_bottom_of_the_stack.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_top_of_the_stack.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_top_of_the_stack.plcrash new file mode 100644 index 0000000000..5b1f37827e Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_smash_the_top_of_the_stack.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_stack_overflow.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_stack_overflow.plcrash new file mode 100644 index 0000000000..2ccd8810dd Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_stack_overflow.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_swift.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_swift.plcrash new file mode 100644 index 0000000000..8eb009d294 Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_swift.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_throw_cpp_exception.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_throw_cpp_exception.plcrash new file mode 100644 index 0000000000..7ba6ca085c Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_throw_cpp_exception.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_write_to_readonly_page.plcrash b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_write_to_readonly_page.plcrash new file mode 100644 index 0000000000..253d17cb4c Binary files /dev/null and b/AppCenterCrashes/AppCenterCrashesTests/Fixtures/CrashProbeMacOS/macOS_report_write_to_readonly_page.plcrash differ diff --git a/AppCenterCrashes/AppCenterCrashesTests/MSCrashesTests.mm b/AppCenterCrashes/AppCenterCrashesTests/MSCrashesTests.mm index 909aabaa92..f16d5034af 100644 --- a/AppCenterCrashes/AppCenterCrashesTests/MSCrashesTests.mm +++ b/AppCenterCrashes/AppCenterCrashesTests/MSCrashesTests.mm @@ -2,11 +2,11 @@ #import "MSAppleErrorLog.h" #import "MSChannelUnitConfiguration.h" #import "MSChannelUnitDefault.h" +#import "MSCrashHandlerSetupDelegate.h" #import "MSCrashesInternal.h" #import "MSCrashesPrivate.h" #import "MSCrashesTestUtil.h" #import "MSCrashesUtil.h" -#import "MSCrashHandlerSetupDelegate.h" #import "MSErrorAttachmentLogInternal.h" #import "MSErrorLogFormatter.h" #import "MSException.h" @@ -129,7 +129,7 @@ - (void)testDelegateMethodsAreCalled { // If id delegateMock = OCMProtocolMock(@protocol(MSCrashesDelegate)); [MSAppCenter sharedInstance].sdkConfigured = NO; - [MSAppCenter start:kMSTestAppSecret withServices:@[ [MSCrashes class] ]]; + [MSAppCenter start:kMSTestAppSecret withServices:@ [[MSCrashes class]]]; MSAppleErrorLog *errorLog = OCMClassMock([MSAppleErrorLog class]); MSErrorReport *errorReport = OCMClassMock([MSErrorReport class]); id errorLogFormatterMock = OCMClassMock([MSErrorLogFormatter class]); @@ -308,7 +308,7 @@ - (void)testProcessCrashesWithErrorAttachments { }]]) .andReturn(channelUnitMock); for (NSUInteger i = 0; i < invalidLogs.count; i++) { - OCMReject([channelUnitMock enqueueItem:invalidLogs[i]]); + OCMReject([channelUnitMock enqueueItem:invalidLogs[i] flags:MSFlagsDefault]); } MSErrorAttachmentLog *validLog = [self attachmentWithAttachmentId:validString attachmentData:validData contentType:validString]; NSMutableArray *logs = invalidLogs.mutableCopy; @@ -320,7 +320,7 @@ - (void)testProcessCrashesWithErrorAttachments { [self.sut setDelegate:crashesDelegateMock]; // Then - OCMExpect([channelUnitMock enqueueItem:validLog]); + OCMExpect([channelUnitMock enqueueItem:validLog flags:MSFlagsDefault]); [self.sut startCrashProcessing]; OCMVerifyAll(channelUnitMock); } @@ -438,22 +438,22 @@ - (void)testEmptyLogBufferFiles { XCTAssertTrue([data length] == 0); } -- (void)testBufferIndexIncrementForAllPriorities { +- (void)testBufferIndexIncrement { // When MSLogWithProperties *log = [MSLogWithProperties new]; - [self.sut channel:nil didPrepareLog:log withInternalId:MS_UUID_STRING]; + [self.sut channel:nil didPrepareLog:log internalId:MS_UUID_STRING flags:MSFlagsPersistenceNormal]; // Then XCTAssertTrue([self crashesLogBufferCount] == 1); } -- (void)testBufferIndexOverflowForAllPriorities { +- (void)testBufferIndexOverflow { // When for (int i = 0; i < ms_crashes_log_buffer_size; i++) { MSLogWithProperties *log = [MSLogWithProperties new]; - [self.sut channel:nil didPrepareLog:log withInternalId:MS_UUID_STRING]; + [self.sut channel:nil didPrepareLog:log internalId:MS_UUID_STRING flags:MSFlagsDefault]; } // Then @@ -461,7 +461,7 @@ - (void)testBufferIndexOverflowForAllPriorities { // When MSLogWithProperties *log = [MSLogWithProperties new]; - [self.sut channel:nil didPrepareLog:log withInternalId:MS_UUID_STRING]; + [self.sut channel:nil didPrepareLog:log internalId:MS_UUID_STRING flags:MSFlagsDefault]; NSNumberFormatter *timestampFormatter = [[NSNumberFormatter alloc] init]; timestampFormatter.numberStyle = NSNumberFormatterDecimalStyle; int indexOfLatestObject = 0; @@ -483,7 +483,7 @@ - (void)testBufferIndexOverflowForAllPriorities { // When for (int i = 0; i < numberOfLogs; i++) { MSLogWithProperties *aLog = [MSLogWithProperties new]; - [self.sut channel:nil didPrepareLog:aLog withInternalId:MS_UUID_STRING]; + [self.sut channel:nil didPrepareLog:aLog internalId:MS_UUID_STRING flags:MSFlagsDefault]; } indexOfLatestObject = 0; @@ -510,29 +510,29 @@ - (void)testBufferIndexOnPersistingLog { NSString *uuid1 = MS_UUID_STRING; NSString *uuid2 = MS_UUID_STRING; NSString *uuid3 = MS_UUID_STRING; - [self.sut channel:nil didPrepareLog:[MSLogWithProperties new] withInternalId:uuid1]; - [self.sut channel:nil didPrepareLog:commonSchemaLog withInternalId:uuid2]; + [self.sut channel:nil didPrepareLog:[MSLogWithProperties new] internalId:uuid1 flags:MSFlagsDefault]; + [self.sut channel:nil didPrepareLog:commonSchemaLog internalId:uuid2 flags:MSFlagsDefault]; // Don't buffer event if log is related to crash. - [self.sut channel:nil didPrepareLog:[MSAppleErrorLog new] withInternalId:uuid3]; + [self.sut channel:nil didPrepareLog:[MSAppleErrorLog new] internalId:uuid3 flags:MSFlagsDefault]; // Then assertThatLong([self crashesLogBufferCount], equalToLong(2)); // When - [self.sut channel:nil didCompleteEnqueueingLog:nil withInternalId:uuid3]; + [self.sut channel:nil didCompleteEnqueueingLog:nil internalId:uuid3]; // Then assertThatLong([self crashesLogBufferCount], equalToLong(2)); // When - [self.sut channel:nil didCompleteEnqueueingLog:nil withInternalId:uuid2]; + [self.sut channel:nil didCompleteEnqueueingLog:nil internalId:uuid2]; // Then assertThatLong([self crashesLogBufferCount], equalToLong(1)); // When - [self.sut channel:nil didCompleteEnqueueingLog:nil withInternalId:uuid1]; + [self.sut channel:nil didCompleteEnqueueingLog:nil internalId:uuid1]; // Then assertThatLong([self crashesLogBufferCount], equalToLong(0)); @@ -548,7 +548,7 @@ - (void)testLogBufferSave { return [configuration.groupId isEqualToString:@"CrashesBuffer"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY]).andDo(^(__unused NSInvocation *invocation) { + OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(__unused NSInvocation *invocation) { numInvocations++; }); @@ -558,11 +558,11 @@ - (void)testLogBufferSave { NSString *uuid1 = MS_UUID_STRING; NSString *uuid2 = MS_UUID_STRING; NSString *uuid3 = MS_UUID_STRING; - [self.sut channel:nil didPrepareLog:[MSLogWithProperties new] withInternalId:uuid1]; - [self.sut channel:nil didPrepareLog:commonSchemaLog withInternalId:uuid2]; + [self.sut channel:nil didPrepareLog:[MSLogWithProperties new] internalId:uuid1 flags:MSFlagsDefault]; + [self.sut channel:nil didPrepareLog:commonSchemaLog internalId:uuid2 flags:MSFlagsDefault]; // Don't buffer event if log is related to crash. - [self.sut channel:nil didPrepareLog:[MSAppleErrorLog new] withInternalId:uuid3]; + [self.sut channel:nil didPrepareLog:[MSAppleErrorLog new] internalId:uuid3 flags:MSFlagsDefault]; // Then assertThatLong([self crashesLogBufferCount], equalToLong(2)); @@ -690,13 +690,14 @@ - (void)testTrackModelExceptionWithoutProperties { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - MSHandledErrorLog *log; - [invocation getArgument:&log atIndex:2]; - type = log.type; - errorId = log.errorId; - exception = log.exception; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + MSHandledErrorLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + errorId = log.errorId; + exception = log.exception; + }); [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; [[MSCrashes sharedInstance] startWithChannelGroup:channelGroupMock @@ -730,14 +731,15 @@ - (void)testTrackModelExceptionWithProperties { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSAbstractLog class]]]).andDo(^(NSInvocation *invocation) { - MSHandledErrorLog *log; - [invocation getArgument:&log atIndex:2]; - type = log.type; - errorId = log.errorId; - exception = log.exception; - properties = log.properties; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSAbstractLog class]] flags:MSFlagsDefault]) + .andDo(^(NSInvocation *invocation) { + MSHandledErrorLog *log; + [invocation getArgument:&log atIndex:2]; + type = log.type; + errorId = log.errorId; + exception = log.exception; + properties = log.properties; + }); [MSAppCenter configureWithAppSecret:kMSTestAppSecret]; [[MSCrashes sharedInstance] startWithChannelGroup:channelGroupMock appSecret:kMSTestAppSecret @@ -749,7 +751,7 @@ - (void)testTrackModelExceptionWithProperties { expectedException.message = @"Oh this is wrong..."; expectedException.stackTrace = @"mock stacktrace"; expectedException.type = @"Some.Exception"; - NSDictionary *expectedProperties = @{ @"milk" : @"yes", @"cookie" : @"of course" }; + NSDictionary *expectedProperties = @{@"milk" : @"yes", @"cookie" : @"of course"}; [MSCrashes trackModelException:expectedException withProperties:expectedProperties]; // Then @@ -777,10 +779,11 @@ - (void)testSendOrAwaitWhenAlwaysSendIsTrue { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - (void)invocation; - numInvocations++; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsPersistenceCritical]) + .andDo(^(NSInvocation *invocation) { + (void)invocation; + numInvocations++; + }); [self startCrashes:self.sut withReports:YES withChannelGroup:channelGroupMock]; NSMutableArray *reportIds = [self idListFromReports:[self.sut unprocessedCrashReports]]; @@ -808,10 +811,11 @@ - (void)testSendOrAwaitWhenAlwaysSendIsFalseAndNotifyAlwaysSend { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - (void)invocation; - numInvocations++; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsPersistenceCritical]) + .andDo(^(NSInvocation *invocation) { + (void)invocation; + numInvocations++; + }); [self startCrashes:self.sut withReports:YES withChannelGroup:channelGroupMock]; NSMutableArray *reports = [self idListFromReports:[self.sut unprocessedCrashReports]]; @@ -845,10 +849,11 @@ - (void)testSendOrAwaitWhenAlwaysSendIsFalseAndNotifySend { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - (void)invocation; - numInvocations++; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsPersistenceCritical]) + .andDo(^(NSInvocation *invocation) { + (void)invocation; + numInvocations++; + }); [self startCrashes:self.sut withReports:YES withChannelGroup:channelGroupMock]; NSMutableArray *reportIds = [self idListFromReports:[self.sut unprocessedCrashReports]]; @@ -883,10 +888,11 @@ - (void)testSendOrAwaitWhenAlwaysSendIsFalseAndNotifyDontSend { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]]]).andDo(^(NSInvocation *invocation) { - (void)invocation; - numInvocations++; - }); + OCMStub([channelUnitMock enqueueItem:[OCMArg isKindOfClass:[MSLogWithProperties class]] flags:MSFlagsPersistenceCritical]) + .andDo(^(NSInvocation *invocation) { + (void)invocation; + numInvocations++; + }); NSMutableArray *reportIds = [self idListFromReports:[self.sut unprocessedCrashReports]]; // When @@ -935,7 +941,7 @@ - (void)testSendErrorAttachments { return [configuration.groupId isEqualToString:@"Crashes"]; }]]) .andReturn(channelUnitMock); - OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY]).andDo(^(NSInvocation *invocation) { + OCMStub([channelUnitMock enqueueItem:OCMOCK_ANY flags:MSFlagsDefault]).andDo(^(NSInvocation *invocation) { numInvocations++; MSErrorAttachmentLog *attachmentLog; [invocation getArgument:&attachmentLog atIndex:2]; diff --git a/AppCenterCrashes/AppCenterCrashesTests/MSErrorLogFormatterTests.mm b/AppCenterCrashes/AppCenterCrashesTests/MSErrorLogFormatterTests.mm index 0e6eb3bc96..e8881cab9e 100644 --- a/AppCenterCrashes/AppCenterCrashesTests/MSErrorLogFormatterTests.mm +++ b/AppCenterCrashes/AppCenterCrashesTests/MSErrorLogFormatterTests.mm @@ -12,6 +12,35 @@ #import "MSTestFrameworks.h" #import "MSThread.h" +static NSString *kFixture = @"fixtureName"; +static NSString *kThreadNumber = @"crashedThreadNumber"; +static NSString *kFramesCount = @"crashedThreadStackFrames"; +static NSString *kBinariesCount = @"binariesCount"; + +static NSArray *kMacOSCrashReportsParameters = @[ + @{ kThreadNumber:@0, kFramesCount:@21, kBinariesCount:@10, kFixture:@"macOS_report_abort" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_builtin_trap" }, + @{ kThreadNumber:@0, kFramesCount:@30, kBinariesCount:@11, kFixture:@"macOS_report_corrupt_malloc_internal_info" }, + @{ kThreadNumber:@0, kFramesCount:@21, kBinariesCount:@10, kFixture:@"macOS_report_corrupt_objc_runtime_structure" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_dereference_bad_pointer" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_dereference_null_pointer" }, + @{ kThreadNumber:@0, kFramesCount:@21, kBinariesCount:@9, kFixture:@"macOS_report_dwarf_unwinding" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_execute_privileged_instruction" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_execute_undefined_instruction" }, + @{ kThreadNumber:@0, kFramesCount:@20, kBinariesCount:@9, kFixture:@"macOS_report_jump_into_nx_page" }, + @{ kThreadNumber:@0, kFramesCount:@26, kBinariesCount:@13, kFixture:@"macOS_report_objc_access_non_object_as_object" }, + @{ kThreadNumber:@0, kFramesCount:@20, kBinariesCount:@12, kFixture:@"macOS_report_objc_crash_inside_msgsend" }, + @{ kThreadNumber:@0, kFramesCount:@21, kBinariesCount:@12, kFixture:@"macOS_report_objc_message_released_object" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@11, kFixture:@"macOS_report_overwrite_link_register" }, + @{ kThreadNumber:@0, kFramesCount:@21, kBinariesCount:@10, kFixture:@"macOS_report_pthread_lock" }, + @{ kThreadNumber:@0, kFramesCount:@1, kBinariesCount:@9, kFixture:@"macOS_report_smash_the_bottom_of_the_stack" }, + @{ kThreadNumber:@0, kFramesCount:@1, kBinariesCount:@10, kFixture:@"macOS_report_smash_the_top_of_the_stack" }, + @{ kThreadNumber:@0, kFramesCount:@512, kBinariesCount:@8, kFixture:@"macOS_report_stack_overflow" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_swift" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@13, kFixture:@"macOS_report_throw_cpp_exception" }, + @{ kThreadNumber:@0, kFramesCount:@19, kBinariesCount:@9, kFixture:@"macOS_report_write_to_readonly_page" } +]; + @interface MSErrorLogFormatter () + (NSString *)selectorForRegisterWithName:(NSString *)regName ofThread:(MSPLCrashReportThreadInfo *)thread report:(MSPLCrashReport *)report; @@ -541,19 +570,26 @@ - (void)testBinaryImageCountFromReportIsCorrect { #endif #if TARGET_OS_OSX -- (void)testBinaryImageCountFromMacOSReportIsCorrect { - - // If - NSData *crashData = [MSCrashesTestUtil dataOfFixtureCrashReportWithFileName:@"macOS_report_pthread_lock"]; - NSError *error = nil; - MSPLCrashReport *crashReport = [[MSPLCrashReport alloc] initWithData:crashData error:&error]; - NSUInteger expectedCount = 10; - - // When - NSArray *binaryImages = [MSErrorLogFormatter extractBinaryImagesFromReport:crashReport codeType:@(CPU_TYPE_ARM64) is64bit:YES]; - - // Then - XCTAssertEqual(expectedCount, binaryImages.count); +- (void)testCrashReportsParametersFromMacOSReport { + for (unsigned long i = 0; i < kMacOSCrashReportsParameters.count; i++) { + + // If + NSData *crashData = [MSCrashesTestUtil dataOfFixtureCrashReportWithFileName:kMacOSCrashReportsParameters[i][kFixture]]; + NSError *error = nil; + MSPLCrashReport *crashReport = [[MSPLCrashReport alloc] initWithData:crashData error:&error]; + + // When + NSArray *binaryImages = [MSErrorLogFormatter extractBinaryImagesFromReport:crashReport codeType:@(CPU_TYPE_ARM64) is64bit:YES]; + MSPLCrashReportThreadInfo *crashedThread = [MSErrorLogFormatter findCrashedThreadInReport:crashReport]; + + // Then + int expectedBinariesCount = [kMacOSCrashReportsParameters[i][kBinariesCount] intValue]; + int expectedThreadNumber = [kMacOSCrashReportsParameters[i][kThreadNumber] intValue]; + int expectedFramesCount = [kMacOSCrashReportsParameters[i][kFramesCount] intValue]; + XCTAssertEqual(expectedBinariesCount, binaryImages.count); + XCTAssertEqual(expectedThreadNumber, crashedThread.threadNumber); + XCTAssertEqual(expectedFramesCount, crashedThread.stackFrames.count); + } } #endif diff --git a/AppCenterDistribute/AppCenterDistribute.xcodeproj/project.pbxproj b/AppCenterDistribute/AppCenterDistribute.xcodeproj/project.pbxproj index e17b5f2634..020f93d1cc 100644 --- a/AppCenterDistribute/AppCenterDistribute.xcodeproj/project.pbxproj +++ b/AppCenterDistribute/AppCenterDistribute.xcodeproj/project.pbxproj @@ -95,6 +95,7 @@ B2C070C51E5E6A200076D6A9 /* AppCenterDistributeResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B2C070B81E5E68540076D6A9 /* AppCenterDistributeResources.bundle */; }; BA682AC831677E0806CA2359 /* MSErrorDetails.h in Headers */ = {isa = PBXBuildFile; fileRef = BA6820A043C816916AF34A04 /* MSErrorDetails.h */; }; BA682C81BA750CA5FCB63A51 /* MSErrorDetails.m in Sources */ = {isa = PBXBuildFile; fileRef = BA6826E3C58372A269A6B43A /* MSErrorDetails.m */; }; + F82E4C83217F217F00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C82217F217F00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; F8BEA3CA1FBB0A6E00D4DE37 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8BEA3C81FBB0A5900D4DE37 /* OHHTTPStubs.framework */; }; F8BEA3CF1FBB0BE400D4DE37 /* MSHttpTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BEA3CB1FBB0BDE00D4DE37 /* MSHttpTestUtil.m */; }; /* End PBXBuildFile section */ @@ -228,6 +229,7 @@ B2C070BA1E5E68540076D6A9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; BA6820A043C816916AF34A04 /* MSErrorDetails.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSErrorDetails.h; sourceTree = ""; }; BA6826E3C58372A269A6B43A /* MSErrorDetails.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSErrorDetails.m; sourceTree = ""; }; + F82E4C82217F217F00EDAB34 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; F8646E651F349C6D006080AC /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = Resources/ru.lproj/AppCenterDistribute.strings; sourceTree = ""; }; F8B8EAE621106436004BCECB /* Resources.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Resources.xcconfig; sourceTree = ""; }; F8BEA3C81FBB0A5900D4DE37 /* OHHTTPStubs.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = OHHTTPStubs.framework; sourceTree = ""; }; @@ -437,6 +439,7 @@ 04FD4A491E453930009B4468 /* Frameworks */ = { isa = PBXGroup; children = ( + F82E4C82217F217F00EDAB34 /* sqlite3.c */, 04FD4A4A1E453930009B4468 /* libAppCenter.a */, 380304401E7B007E006A55FB /* OCHamcrest */, 380304321E7B007E006A55FB /* OCMock */, @@ -782,6 +785,7 @@ buildActionMask = 2147483647; files = ( 040AF0251E523B80005C1174 /* MSReleaseDetailsTests.m in Sources */, + F82E4C83217F217F00EDAB34 /* sqlite3.c in Sources */, B20DE7B71E5F97330023075C /* MSDistributeUtilTests.m in Sources */, 9E8E3A39203E68B4001A23FD /* MSDistributionStartSessionLogTest.m in Sources */, 387CC631210A9D9C002BA068 /* MSMockKeychainUtil.m in Sources */, diff --git a/AppCenterDistribute/AppCenterDistribute/Internals/MSDistributeAppDelegate.m b/AppCenterDistribute/AppCenterDistribute/Internals/MSDistributeAppDelegate.m index 635b44af29..4762b9730a 100644 --- a/AppCenterDistribute/AppCenterDistribute/Internals/MSDistributeAppDelegate.m +++ b/AppCenterDistribute/AppCenterDistribute/Internals/MSDistributeAppDelegate.m @@ -1,6 +1,6 @@ +#import "MSDistributeAppDelegate.h" #import "MSAppDelegateForwarder.h" #import "MSDistribute.h" -#import "MSDistributeAppDelegate.h" @implementation MSDistributeAppDelegate @@ -39,8 +39,8 @@ @implementation MSAppDelegateForwarder (MSDistribute) + (void)load { // Register selectors to swizzle for Distribute. - [self addAppDelegateSelectorToSwizzle:@selector(application:openURL:options:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:openURL:sourceApplication:annotation:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application:openURL:options:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application:openURL:sourceApplication:annotation:)]; } @end diff --git a/AppCenterDistribute/AppCenterDistribute/MSDistribute.m b/AppCenterDistribute/AppCenterDistribute/MSDistribute.m index 5cd02b35a9..b51a35259e 100644 --- a/AppCenterDistribute/AppCenterDistribute/MSDistribute.m +++ b/AppCenterDistribute/AppCenterDistribute/MSDistribute.m @@ -124,7 +124,7 @@ - (void)applyEnabledState:(BOOL)isEnabled { MSLogInfo([MSDistribute logTag], @"Distribute service has been enabled."); self.releaseDetails = nil; [self startUpdate]; - [MSAppDelegateForwarder addDelegate:self.appDelegate]; + [[MSAppDelegateForwarder sharedInstance] addDelegate:self.appDelegate]; // Enable the distribute info tracker. [self.channelGroup addDelegate:self.distributeInfoTracker]; @@ -138,7 +138,7 @@ - (void)applyEnabledState:(BOOL)isEnabled { } else { [self dismissEmbeddedSafari]; [self.channelGroup removeDelegate:self.distributeInfoTracker]; - [MSAppDelegateForwarder removeDelegate:self.appDelegate]; + [[MSAppDelegateForwarder sharedInstance] removeDelegate:self.appDelegate]; [MS_USER_DEFAULTS removeObjectForKey:kMSUpdateTokenRequestIdKey]; [MS_USER_DEFAULTS removeObjectForKey:kMSPostponedTimestampKey]; [MS_USER_DEFAULTS removeObjectForKey:kMSMandatoryReleaseKey]; @@ -225,7 +225,7 @@ - (void)sendFirstSessionUpdateLog { MSDistributionStartSessionLog *log = [[MSDistributionStartSessionLog alloc] init]; // Send log to log manager. - [self.channelUnit enqueueItem:log]; + [self.channelUnit enqueueItem:log flags:MSFlagsDefault]; } - (void)startUpdate { @@ -251,8 +251,8 @@ - (void)requestInstallInformationWith:(NSString *)releaseHash { // Check if the device has internet connection to get update token. if ([MS_Reachability reachabilityForInternetConnection].currentReachabilityStatus == NotReachable) { - MSLogWarning([MSDistribute logTag], @"The device lost its internet connection. The SDK will " - @"retry to get an update token in the next launch."); + MSLogWarning([MSDistribute logTag], + @"The device lost its internet connection. The SDK will retry to get an update token in the next launch."); return; } @@ -263,14 +263,10 @@ - (void)requestInstallInformationWith:(NSString *)releaseHash { NSString *updateSetupFailedPackageHash = [MS_USER_DEFAULTS objectForKey:kMSUpdateSetupFailedPackageHashKey]; if (updateSetupFailedPackageHash) { if ([updateSetupFailedPackageHash isEqualToString:releaseHash]) { - MSLogDebug([MSDistribute logTag], @"Skipping in-app updates setup, " - @"because it already failed on this " - @"release before."); + MSLogDebug([MSDistribute logTag], @"Skipping in-app updates setup, because it already failed on this release before."); return; } else { - MSLogDebug([MSDistribute logTag], @"Re-attempting in-app updates setup " - @"and cleaning up failure info from " - @"storage."); + MSLogDebug([MSDistribute logTag], @"Re-attempting in-app updates setup and cleaning up failure info from storage."); [MS_USER_DEFAULTS removeObjectForKey:kMSUpdateSetupFailedPackageHashKey]; [MS_USER_DEFAULTS removeObjectForKey:kMSTesterAppUpdateSetupFailedKey]; } @@ -294,13 +290,9 @@ - (void)requestInstallInformationWith:(NSString *)releaseHash { if (testerAppUrl) { testerAppOpened = [self openUrlUsingSharedApp:testerAppUrl]; if (testerAppOpened) { - MSLogInfo([MSDistribute logTag], @"Tester app was successfully " - @"opened to enable in-app " - @"updates."); + MSLogInfo([MSDistribute logTag], @"Tester app was successfully opened to enable in-app updates."); } else { - MSLogInfo([MSDistribute logTag], @"Tester app could not be opened " - @"to enable in-app updates (not " - @"installed?)"); + MSLogInfo([MSDistribute logTag], @"Tester app could not be opened to enable in-app updates (not installed?)"); } } } @@ -414,14 +406,15 @@ - (void)checkLatestRelease:(NSString *)updateToken distributionGroupId:(NSString // Failure can deliver non-JSON format of payload. if (!jsonError) { - NSData *jsonData = - [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&jsonError]; + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary + options:NSJSONWritingPrettyPrinted + error:&jsonError]; if (jsonData && !jsonError) { // NSJSONSerialization escapes paths by default so we replace them. - jsonString = [[[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding] - stringByReplacingOccurrencesOfString:@"\\/" - withString:@"/"]; + jsonString = [[[NSString alloc] initWithData:jsonData + encoding:NSUTF8StringEncoding] stringByReplacingOccurrencesOfString:@"\\/" + withString:@"/"]; } } } @@ -683,8 +676,8 @@ - (BOOL)handleUpdate:(MSReleaseDetails *)details { long long duration = (long long)[MSUtility nowInMilliseconds] - [postponedTimestamp longLongValue]; if (duration >= 0 && duration < kMSDayInMillisecond) { if (details.mandatoryUpdate) { - MSLogDebug([MSDistribute logTag], @"The update was postponed within a day ago but the update is a mandatory update. " - @"The SDK will proceed update for the release."); + MSLogDebug([MSDistribute logTag], @"The update was postponed within a day ago but the update is a mandatory update. The SDK will " + @"proceed update for the release."); } else { MSLogDebug([MSDistribute logTag], @"The update was postponed within a day ago, skip update."); return NO; @@ -765,9 +758,7 @@ - (void)storeDownloadedReleaseDetails:(nullable MSReleaseDetails *)details { [MS_USER_DEFAULTS setObject:groupId forKey:kMSDownloadedDistributionGroupIdKey]; [MS_USER_DEFAULTS setObject:releaseId forKey:kMSDownloadedReleaseIdKey]; [MS_USER_DEFAULTS setObject:releaseHashes forKey:kMSDownloadedReleaseHashKey]; - MSLogDebug([MSDistribute logTag], @"Stored downloaded release hash(es) (%@) and id (%@) for later " - @"reporting.", - releaseHashes, releaseId); + MSLogDebug([MSDistribute logTag], @"Stored downloaded release hash(es) (%@) and id (%@) for later reporting.", releaseHashes, releaseId); } - (void)removeDownloadedReleaseDetailsIfUpdated:(NSString *)currentInstalledReleaseHash { @@ -776,8 +767,9 @@ - (void)removeDownloadedReleaseDetailsIfUpdated:(NSString *)currentInstalledRele return; } if ([lastDownloadedReleaseHashes rangeOfString:currentInstalledReleaseHash].location == NSNotFound) { - MSLogDebug([MSDistribute logTag], @"Stored release hash(es) (%@) doesn't match current installation hash (%@), " - @"probably downloaded but not installed yet, keep in store.", + MSLogDebug([MSDistribute logTag], + @"Stored release hash(es) (%@) doesn't match current installation hash (%@), " + @"probably downloaded but not installed yet, keep in store.", lastDownloadedReleaseHashes, currentInstalledReleaseHash); return; } @@ -836,8 +828,7 @@ - (void)changeDistributionGroupIdAfterAppUpdateIfNeeded:(NSString *)currentInsta if ((storedDistributionGroupId == nil) || ([updatedReleaseDistributionGroupId isEqualToString:storedDistributionGroupId] == NO)) { // Set group ID from downloaded release details if an updated release was downloaded from another distribution group. - MSLogDebug([MSDistribute logTag], @"Stored group ID doesn't match the group ID of the updated " - @"release, updating group id: %@", + MSLogDebug([MSDistribute logTag], @"Stored group ID doesn't match the group ID of the updated release, updating group id: %@", updatedReleaseDistributionGroupId); [MS_USER_DEFAULTS setObject:updatedReleaseDistributionGroupId forKey:kMSDistributionGroupIdKey]; } @@ -850,7 +841,6 @@ - (void)showConfirmationAlert:(MSReleaseDetails *)details { // Displaying alert dialog. Running on main thread. dispatch_async(dispatch_get_main_queue(), ^{ - // Init the alert controller. NSString *messageFormat = details.mandatoryUpdate ? MSDistributeLocalizedString(@"MSDistributeAppUpdateAvailableMandatoryUpdateMessage") : MSDistributeLocalizedString(@"MSDistributeAppUpdateAvailableOptionalUpdateMessage"); @@ -881,10 +871,8 @@ - (void)showConfirmationAlert:(MSReleaseDetails *)details { // Add a "View release notes"-Button. [alertController addDefaultActionWithTitle:MSDistributeLocalizedString(@"MSDistributeViewReleaseNotes") handler:^(__attribute__((unused)) UIAlertAction *action) { - MSLogDebug([MSDistribute logTag], @"'View release notes' is selected. " - @"Open a browser and show release " - @"notes."); - + MSLogDebug([MSDistribute logTag], + @"'View release notes' is selected. Open a browser and show release notes."); [MSUtility sharedAppOpenUrl:details.releaseNotesUrl options:@{} completionHandler:nil]; /* @@ -945,8 +933,8 @@ - (void)showUpdateSetupFailedAlert:(NSString *)errorMessage { * Add a flag to the install url to indicate that the update setup failed, to show a help page */ NSURLComponents *components = [[NSURLComponents alloc] initWithURL:installUrl resolvingAgainstBaseURL:NO]; - NSURLQueryItem *newQueryItem = - [[NSURLQueryItem alloc] initWithName:kMSURLQueryUpdateSetupFailedKey value:@"true"]; + NSURLQueryItem *newQueryItem = [[NSURLQueryItem alloc] initWithName:kMSURLQueryUpdateSetupFailedKey + value:@"true"]; NSMutableArray *newQueryItems = [NSMutableArray arrayWithCapacity:[components.queryItems count] + 1]; for (NSURLQueryItem *qi in components.queryItems) { if (![qi.name isEqual:newQueryItem.name]) { @@ -970,8 +958,9 @@ - (void)showUpdateSetupFailedAlert:(NSString *)errorMessage { } - (void)startDownload:(nullable MSReleaseDetails *)details { - [MSUtility sharedAppOpenUrl:details.installUrl - options:@{} + [MSUtility + sharedAppOpenUrl:details.installUrl + options:@{} completionHandler:^(MSOpenURLState state) { switch (state) { case MSOpenURLStateSucceed: @@ -1058,9 +1047,7 @@ - (BOOL)openURL:(NSURL *)url { // Store distribution group ID. if (queryDistributionGroupId) { - MSLogDebug([MSDistribute logTag], @"Distribution group ID has been " - @"successfully retrieved. Store the " - @"ID to storage."); + MSLogDebug([MSDistribute logTag], @"Distribution group ID has been successfully retrieved. Store the ID to storage."); // Storing the distribution group ID to storage. [MS_USER_DEFAULTS setObject:queryDistributionGroupId forKey:kMSDistributionGroupIdKey]; @@ -1082,9 +1069,7 @@ - (BOOL)openURL:(NSURL *)url { * Update token is used only for private distribution. If the query parameters don't include update token, it is public distribution. */ if (queryUpdateToken) { - MSLogDebug([MSDistribute logTag], @"Update token has been successfully " - @"retrieved. Store the token to " - @"secure storage."); + MSLogDebug([MSDistribute logTag], @"Update token has been successfully retrieved. Store the token to secure storage."); // Storing the update token to keychain since the update token is considered as a sensitive information. [MSKeychainUtil storeString:queryUpdateToken forKey:kMSUpdateTokenKey]; diff --git a/AppCenterPush/AppCenterPush.xcodeproj/project.pbxproj b/AppCenterPush/AppCenterPush.xcodeproj/project.pbxproj index 44c75a1497..6402a74b33 100644 --- a/AppCenterPush/AppCenterPush.xcodeproj/project.pbxproj +++ b/AppCenterPush/AppCenterPush.xcodeproj/project.pbxproj @@ -81,15 +81,23 @@ 047919B71EBA42F0004834A6 /* MSPushNotification.m in Sources */ = {isa = PBXBuildFile; fileRef = 047919B61EBA42F0004834A6 /* MSPushNotification.m */; }; 047919B91EBA4C44004834A6 /* MSPushNotificationInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 047919B81EBA4C44004834A6 /* MSPushNotificationInternal.h */; }; 04849D791E9E913A0093ECA6 /* MSPushLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 04849D781E9E913A0093ECA6 /* MSPushLogTests.m */; }; - 04873E851F2FD0B600A13AFF /* MSPushAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 382144391EC4D3EC009A6143 /* MSPushAppDelegate.h */; }; - 04873E861F2FD0D900A13AFF /* MSPushAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3821443A1EC4D3EC009A6143 /* MSPushAppDelegate.m */; }; 0496A67B1F3128CE005AE1DB /* MSPushTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D3AD5EC21E5C4F6A001627AB /* MSPushTests.m */; }; 04A0820C1F74BBCA00DC776D /* MSService.h in Headers */ = {isa = PBXBuildFile; fileRef = 04A0820B1F74BBCA00DC776D /* MSService.h */; settings = {ATTRIBUTES = (Public, ); }; }; 04A0820D1F74BBCF00DC776D /* MSService.h in Headers */ = {isa = PBXBuildFile; fileRef = 04A0820B1F74BBCA00DC776D /* MSService.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 3821443B1EC4D3EC009A6143 /* MSPushAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 382144391EC4D3EC009A6143 /* MSPushAppDelegate.h */; }; - 3821443C1EC4D3EC009A6143 /* MSPushAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 3821443A1EC4D3EC009A6143 /* MSPushAppDelegate.m */; }; 38437AD61FF3FF47006964AE /* MSCustomPushApplicationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38437AD51FF3FF47006964AE /* MSCustomPushApplicationDelegate.h */; }; 38437AD71FF3FF47006964AE /* MSCustomPushApplicationDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38437AD51FF3FF47006964AE /* MSCustomPushApplicationDelegate.h */; }; + 384A925D2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A925C2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m */; }; + 384A925E2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A925C2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m */; }; + 384A92662188C65C0099BE70 /* MSDelegateForwarderTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A92652188C65C0099BE70 /* MSDelegateForwarderTestUtil.m */; }; + 384A92672188C65C0099BE70 /* MSDelegateForwarderTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 384A92652188C65C0099BE70 /* MSDelegateForwarderTestUtil.m */; }; + 38C65D7C21826C400004FB44 /* MSPushAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C65D7A21826C400004FB44 /* MSPushAppDelegate.m */; }; + 38C65D7D21826C400004FB44 /* MSPushAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C65D7A21826C400004FB44 /* MSPushAppDelegate.m */; }; + 38C65D7E21826C400004FB44 /* MSPushAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C65D7B21826C400004FB44 /* MSPushAppDelegate.h */; }; + 38C65D7F21826C400004FB44 /* MSPushAppDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C65D7B21826C400004FB44 /* MSPushAppDelegate.h */; }; + 38C65D8221826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C65D8021826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h */; }; + 38C65D8321826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h in Headers */ = {isa = PBXBuildFile; fileRef = 38C65D8021826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h */; }; + 38C65D8421826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C65D8121826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m */; }; + 38C65D8521826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m in Sources */ = {isa = PBXBuildFile; fileRef = 38C65D8121826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m */; }; 38D3B01F1FF3D2A900E5FAF2 /* MSAppDelegateForwarderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 38D3B01D1FF3D2A400E5FAF2 /* MSAppDelegateForwarderTests.m */; }; D38023C11E6DE01700466558 /* UserNotifications.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D38023C01E6DE01700466558 /* UserNotifications.framework */; }; D39B04E41E66FBD50048E397 /* MSPushTestUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D39B04E31E66FBD50048E397 /* MSPushTestUtil.m */; }; @@ -104,6 +112,8 @@ D3AD5F6D1E5D7464001627AB /* MSPushLog.m in Sources */ = {isa = PBXBuildFile; fileRef = D3AD5F6B1E5D7464001627AB /* MSPushLog.m */; }; D3AD5FDA1E5DDF72001627AB /* libAppCenter.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D3AD5FD91E5DDF72001627AB /* libAppCenter.a */; }; D3D0CFE91E640ABD003015AF /* OCHamcrestIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D3D0CFE81E640ABD003015AF /* OCHamcrestIOS.framework */; }; + F82E4C85217F218D00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C84217F218D00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; + F82E4C86217F218D00EDAB34 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F82E4C84217F218D00EDAB34 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -178,9 +188,14 @@ 04849D781E9E913A0093ECA6 /* MSPushLogTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSPushLogTests.m; sourceTree = ""; }; 049BC81E1ECE333700FB6719 /* iOS.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = iOS.xcconfig; sourceTree = ""; }; 04A0820B1F74BBCA00DC776D /* MSService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSService.h; path = ../AppCenter/AppCenter/MSService.h; sourceTree = ""; }; - 382144391EC4D3EC009A6143 /* MSPushAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MSPushAppDelegate.h; sourceTree = ""; }; - 3821443A1EC4D3EC009A6143 /* MSPushAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSPushAppDelegate.m; sourceTree = ""; }; 38437AD51FF3FF47006964AE /* MSCustomPushApplicationDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MSCustomPushApplicationDelegate.h; sourceTree = ""; }; + 384A925C2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MSUserNotificationCenterDelegateForwarderTest.m; sourceTree = ""; }; + 384A92642188C65C0099BE70 /* MSDelegateForwarderTestUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSDelegateForwarderTestUtil.h; path = ../../../AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.h; sourceTree = ""; }; + 384A92652188C65C0099BE70 /* MSDelegateForwarderTestUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSDelegateForwarderTestUtil.m; path = ../../../AppCenter/AppCenterTests/Util/MSDelegateForwarderTestUtil.m; sourceTree = ""; }; + 38C65D7A21826C400004FB44 /* MSPushAppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSPushAppDelegate.m; path = DelegateForwarder/MSPushAppDelegate.m; sourceTree = ""; }; + 38C65D7B21826C400004FB44 /* MSPushAppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = MSPushAppDelegate.h; path = DelegateForwarder/MSPushAppDelegate.h; sourceTree = ""; }; + 38C65D8021826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MSUserNotificationCenterDelegateForwarder.h; path = DelegateForwarder/MSUserNotificationCenterDelegateForwarder.h; sourceTree = ""; }; + 38C65D8121826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MSUserNotificationCenterDelegateForwarder.m; path = DelegateForwarder/MSUserNotificationCenterDelegateForwarder.m; sourceTree = ""; }; 38D3B01D1FF3D2A400E5FAF2 /* MSAppDelegateForwarderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MSAppDelegateForwarderTests.m; sourceTree = ""; }; D33A94511E5B11BB003966DB /* libAppCenterPush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAppCenterPush.a; sourceTree = BUILT_PRODUCTS_DIR; }; D33A94541E5B11BB003966DB /* AppCenterPush.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppCenterPush.h; sourceTree = ""; }; @@ -201,6 +216,7 @@ D3AD5F6B1E5D7464001627AB /* MSPushLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = MSPushLog.m; path = Model/MSPushLog.m; sourceTree = ""; }; D3AD5FD91E5DDF72001627AB /* libAppCenter.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libAppCenter.a; path = "../AppCenter/build/Debug-iphoneos/libAppCenter.a"; sourceTree = ""; }; D3D0CFE81E640ABD003015AF /* OCHamcrestIOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCHamcrestIOS.framework; path = ../Vendor/iOS/OCHamcrest/OCHamcrestIOS.framework; sourceTree = ""; }; + F82E4C84217F218D00EDAB34 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -302,6 +318,17 @@ name = iOS; sourceTree = ""; }; + 3862825F21826BFF0023DCCC /* DelegateForwarder */ = { + isa = PBXGroup; + children = ( + 38C65D7B21826C400004FB44 /* MSPushAppDelegate.h */, + 38C65D7A21826C400004FB44 /* MSPushAppDelegate.m */, + 38C65D8021826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h */, + 38C65D8121826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m */, + ); + name = DelegateForwarder; + sourceTree = ""; + }; D33A94481E5B11BB003966DB = { isa = PBXGroup; children = ( @@ -350,6 +377,8 @@ D39B04E11E66FBA70048E397 /* Util */ = { isa = PBXGroup; children = ( + 384A92642188C65C0099BE70 /* MSDelegateForwarderTestUtil.h */, + 384A92652188C65C0099BE70 /* MSDelegateForwarderTestUtil.m */, D39B04E21E66FBC60048E397 /* MSPushTestUtil.h */, D39B04E31E66FBD50048E397 /* MSPushTestUtil.m */, 04311FF91EE08772007054C5 /* MSTestFrameworks.h */, @@ -361,8 +390,9 @@ isa = PBXGroup; children = ( 38D3B01D1FF3D2A400E5FAF2 /* MSAppDelegateForwarderTests.m */, - D3AD5EC41E5C4F6A001627AB /* Info.plist */, + 384A925C2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m */, D3AD5EC21E5C4F6A001627AB /* MSPushTests.m */, + D3AD5EC41E5C4F6A001627AB /* Info.plist */, D3AD5F661E5D73F2001627AB /* Frameworks */, 04A140C21ECE8769001CEE94 /* Model */, 04A140C31ECE877A001CEE94 /* Support */, @@ -387,12 +417,11 @@ D3AD5F071E5C8136001627AB /* Internal */ = { isa = PBXGroup; children = ( - D3AD5F691E5D7423001627AB /* Model */, D3AD5F0B1E5C819A001627AB /* AppCenter+Internal.h */, 38437AD51FF3FF47006964AE /* MSCustomPushApplicationDelegate.h */, D3AD5F081E5C8156001627AB /* MSPushPrivate.h */, - 382144391EC4D3EC009A6143 /* MSPushAppDelegate.h */, - 3821443A1EC4D3EC009A6143 /* MSPushAppDelegate.m */, + 3862825F21826BFF0023DCCC /* DelegateForwarder */, + D3AD5F691E5D7423001627AB /* Model */, ); path = Internal; sourceTree = ""; @@ -400,6 +429,7 @@ D3AD5F661E5D73F2001627AB /* Frameworks */ = { isa = PBXGroup; children = ( + F82E4C84217F218D00EDAB34 /* sqlite3.c */, D3AD5FD91E5DDF72001627AB /* libAppCenter.a */, 0469643F1F2A572800F9D31F /* libAppCenter.a */, 04BED71B1ECB653800E20975 /* OCHamcrest */, @@ -440,8 +470,9 @@ 0469644A1F2A5B6000F9D31F /* MSPushDelegate.h in Headers */, 046964491F2A5B4D00F9D31F /* AppCenter+Internal.h in Headers */, 0469644B1F2A5BC900F9D31F /* MSPush.h in Headers */, - 04873E851F2FD0B600A13AFF /* MSPushAppDelegate.h in Headers */, 38437AD71FF3FF47006964AE /* MSCustomPushApplicationDelegate.h in Headers */, + 38C65D7F21826C400004FB44 /* MSPushAppDelegate.h in Headers */, + 38C65D8321826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h in Headers */, 046964471F2A5B4900F9D31F /* MSPushNotificationInternal.h in Headers */, 0469644D1F2A5D1200F9D31F /* MSPushPrivate.h in Headers */, 0469644E1F2A5D2800F9D31F /* AppCenterPush.h in Headers */, @@ -460,7 +491,8 @@ D3AD5F0C1E5C819A001627AB /* AppCenter+Internal.h in Headers */, 047919B91EBA4C44004834A6 /* MSPushNotificationInternal.h in Headers */, 047919B51EBA3B1B004834A6 /* MSPushDelegate.h in Headers */, - 3821443B1EC4D3EC009A6143 /* MSPushAppDelegate.h in Headers */, + 38C65D7E21826C400004FB44 /* MSPushAppDelegate.h in Headers */, + 38C65D8221826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.h in Headers */, 38437AD61FF3FF47006964AE /* MSCustomPushApplicationDelegate.h in Headers */, D3AD5F101E5C8466001627AB /* MSPush.h in Headers */, D3AD5F121E5C847B001627AB /* MSPushPrivate.h in Headers */, @@ -694,8 +726,9 @@ files = ( 046964481F2A5B4900F9D31F /* MSPushNotification.m in Sources */, 0469644C1F2A5C9200F9D31F /* MSPush.m in Sources */, - 04873E861F2FD0D900A13AFF /* MSPushAppDelegate.m in Sources */, + 38C65D7D21826C400004FB44 /* MSPushAppDelegate.m in Sources */, 046964451F2A5B3E00F9D31F /* MSPushLog.m in Sources */, + 38C65D8521826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -706,6 +739,9 @@ 046964501F2A5D4F00F9D31F /* MSPushLogTests.m in Sources */, 046964511F2A5D8D00F9D31F /* MSPushTestUtil.m in Sources */, 0469644F1F2A5D4A00F9D31F /* MSPushTests.m in Sources */, + 384A925E2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m in Sources */, + F82E4C86217F218D00EDAB34 /* sqlite3.c in Sources */, + 384A92672188C65C0099BE70 /* MSDelegateForwarderTestUtil.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,8 +751,9 @@ files = ( 047919B71EBA42F0004834A6 /* MSPushNotification.m in Sources */, D3AD5F6D1E5D7464001627AB /* MSPushLog.m in Sources */, - 3821443C1EC4D3EC009A6143 /* MSPushAppDelegate.m in Sources */, + 38C65D7C21826C400004FB44 /* MSPushAppDelegate.m in Sources */, D3AD5F111E5C846E001627AB /* MSPush.m in Sources */, + 38C65D8421826CC80004FB44 /* MSUserNotificationCenterDelegateForwarder.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -724,10 +761,13 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 384A925D2188B94F0099BE70 /* MSUserNotificationCenterDelegateForwarderTest.m in Sources */, 0496A67B1F3128CE005AE1DB /* MSPushTests.m in Sources */, 04849D791E9E913A0093ECA6 /* MSPushLogTests.m in Sources */, D39B04E41E66FBD50048E397 /* MSPushTestUtil.m in Sources */, + 384A92662188C65C0099BE70 /* MSDelegateForwarderTestUtil.m in Sources */, 38D3B01F1FF3D2A900E5FAF2 /* MSAppDelegateForwarderTests.m in Sources */, + F82E4C85217F218D00EDAB34 /* sqlite3.c in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/AppCenterPush/AppCenterPush/Internal/MSPushAppDelegate.h b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSPushAppDelegate.h similarity index 100% rename from AppCenterPush/AppCenterPush/Internal/MSPushAppDelegate.h rename to AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSPushAppDelegate.h diff --git a/AppCenterPush/AppCenterPush/Internal/MSPushAppDelegate.m b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSPushAppDelegate.m similarity index 83% rename from AppCenterPush/AppCenterPush/Internal/MSPushAppDelegate.m rename to AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSPushAppDelegate.m index 0b0c424fe3..9380c65610 100644 --- a/AppCenterPush/AppCenterPush/Internal/MSPushAppDelegate.m +++ b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSPushAppDelegate.m @@ -1,6 +1,6 @@ -#import "MSAppDelegateForwarderPrivate.h" -#import "MSPush.h" #import "MSPushAppDelegate.h" +#import "MSAppDelegateForwarder.h" +#import "MSPush.h" @implementation MSPushAppDelegate @@ -45,11 +45,14 @@ @implementation MSAppDelegateForwarder (MSPush) + (void)load { // Register selectors to swizzle for Push. - [self addAppDelegateSelectorToSwizzle:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]; - [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application: + didRegisterForRemoteNotificationsWithDeviceToken:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application: + didFailToRegisterForRemoteNotificationsWithError:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:)]; #if !TARGET_OS_OSX - [self addAppDelegateSelectorToSwizzle:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]; + [[MSAppDelegateForwarder sharedInstance] addDelegateSelectorToSwizzle:@selector(application: + didReceiveRemoteNotification:fetchCompletionHandler:)]; #endif } @@ -57,7 +60,7 @@ - (void)custom_application:(MSApplication *)application didRegisterForRemoteNoti IMP originalImp = NULL; // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; if (originalImp) { ((void (*)(id, SEL, MSApplication *, NSData *))originalImp)(self, _cmd, application, deviceToken); } @@ -70,7 +73,7 @@ - (void)custom_application:(MSApplication *)application didFailToRegisterForRemo IMP originalImp = NULL; // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; if (originalImp) { ((void (*)(id, SEL, MSApplication *, NSError *))originalImp)(self, _cmd, application, error); } @@ -83,7 +86,7 @@ - (void)custom_application:(MSApplication *)application didReceiveRemoteNotifica IMP originalImp = NULL; // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; if (originalImp) { ((void (*)(id, SEL, MSApplication *, NSDictionary *))originalImp)(self, _cmd, application, userInfo); } @@ -106,7 +109,6 @@ - (void)custom_application:(UIApplication *)application // This handler will be used by all the delegates, it unifies the results and execute the real handler at the end. void (^commonCompletionHandler)(UIBackgroundFetchResult, MSCompletionExecutor) = ^(UIBackgroundFetchResult fetchResult, MSCompletionExecutor executor) { - /* * As per the Apple documentation: * https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application @@ -147,14 +149,14 @@ - (void)custom_application:(UIApplication *)application @synchronized([MSAppDelegateForwarder class]) { // Count how many custom delegates will respond to the selector. - for (id delegate in MSAppDelegateForwarder.delegates) { + for (id delegate in [MSAppDelegateForwarder sharedInstance].delegates) { if ([delegate respondsToSelector:_cmd]) { customDelegateToCallCount++; } } // Forward to the original delegate. - [MSAppDelegateForwarder.originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + [[MSAppDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; if (originalImp) { // Completion handler dedicated to the original delegate. diff --git a/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.h b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.h new file mode 100644 index 0000000000..c01f341dc5 --- /dev/null +++ b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.h @@ -0,0 +1,21 @@ +#import "MSDelegateForwarder.h" + +NS_ASSUME_NONNULL_BEGIN + +static NSString *const kMSUserNotificationCenterDelegateForwarderEnabledKey = @"AppCenterUserNotificationCenterDelegateForwarderEnabled"; + +/** + * The @c MSUserNotificationCenterDelegateForwarder is responsible for swizzling the @c UNUserNotificationCenterDelegate and forwarding + * delegate calls to Push and customer implementation. The @c UNUserNotificationCenterDelegate is a push only delegate so the forwarder is + * directly communicating with Push. + */ +@interface MSUserNotificationCenterDelegateForwarder : MSDelegateForwarder + +/** + * This is an empty method to be used to force load this class into the runtime. + */ ++(void)doNothingButForceLoadTheClass; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.m b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.m new file mode 100644 index 0000000000..d36935aedc --- /dev/null +++ b/AppCenterPush/AppCenterPush/Internal/DelegateForwarder/MSUserNotificationCenterDelegateForwarder.m @@ -0,0 +1,136 @@ +#import +#if !TARGET_OS_OSX +#import +#endif + +#import "MSPush.h" +#import "MSUserNotificationCenterDelegateForwarder.h" + +static dispatch_once_t swizzlingOnceToken; + +// Singleton instance. +static MSUserNotificationCenterDelegateForwarder *sharedInstance = nil; + +@implementation MSUserNotificationCenterDelegateForwarder + ++ (void)load { + [[self sharedInstance] setEnabledFromPlistForKey:kMSUserNotificationCenterDelegateForwarderEnabledKey]; + + // TODO test the forwarder on macOS. + // Register selectors to swizzle (iOS 10+). +#if !TARGET_OS_OSX + if ([[MSUserNotificationCenterDelegateForwarder sharedInstance] originalClassForSetDelegate]) { + [[MSUserNotificationCenterDelegateForwarder sharedInstance] + addDelegateSelectorToSwizzle:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]; + [[MSUserNotificationCenterDelegateForwarder sharedInstance] + addDelegateSelectorToSwizzle:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]; + } +#endif +} + ++ (void)doNothingButForceLoadTheClass{ + // This method doesn't need to do anything it's purpose is just to force load this class into the runtime. +} + ++ (instancetype)sharedInstance { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [self new]; + }); + return sharedInstance; +} + ++ (void)resetSharedInstance { + sharedInstance = [self new]; +} + +- (Class)originalClassForSetDelegate { + + // TODO Use @available API when deprecating Xcode 8. + return NSClassFromString(@"UNUserNotificationCenter"); +} + +- (dispatch_once_t *)swizzlingOnceToken { + return &swizzlingOnceToken; +} + +#pragma mark - Custom Application + +#pragma clang diagnostic push + +#if !TARGET_OS_OSX + +// TODO Use @available API and availability attribute when deprecating Xcode 8 then we can try removing these pragma. +#pragma clang diagnostic ignored "-Wpartial-availability" + +- (void)custom_setDelegate:(id)delegate { + + // Swizzle only once. + static dispatch_once_t delegateSwizzleOnceToken; + dispatch_once(&delegateSwizzleOnceToken, ^{ + // Swizzle the delegate object before it's actually set. + [[MSUserNotificationCenterDelegateForwarder sharedInstance] swizzleOriginalDelegate:delegate]; + }); + + // Forward to the original `setDelegate:` implementation. + IMP originalImp = [MSUserNotificationCenterDelegateForwarder sharedInstance].originalSetDelegateImp; + if (originalImp) { + ((void (*)(id, SEL, id))originalImp)(self, _cmd, delegate); + } +} + +#pragma mark - Custom UNUserNotificationCenterDelegate + +- (void)custom_userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { + IMP originalImp = NULL; + + /* + * First, forward to the original delegate, customers can leverage the completion handler later in their Push callback. + * It's now a responsibility of the original delegate to call the completion handler. + */ + [[MSUserNotificationCenterDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + if (originalImp) { + ((void (*)(id, SEL, UNUserNotificationCenter *, UNNotification *, void (^)(UNNotificationPresentationOptions options)))originalImp)( + self, _cmd, center, notification, completionHandler); + } + + // Then, forward to Push. + [MSPush didReceiveRemoteNotification:notification.request.content.userInfo]; + if (!originalImp) { + + // No original implementation, we have to call the completion handler ourselves with the default behavior. + completionHandler(UNNotificationPresentationOptionNone); + } +} + +- (void)custom_userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler { + IMP originalImp = NULL; + + /* + * First, forward to the original delegate, customers can leverage the completion handler later in their Push callback. + * It's now a responsability of the original delegate to call the completion handler. + */ + [[MSUserNotificationCenterDelegateForwarder sharedInstance].originalImplementations[NSStringFromSelector(_cmd)] getValue:&originalImp]; + if (originalImp) { + ((void (*)(id, SEL, UNUserNotificationCenter *, UNNotificationResponse *, void (^)(void)))originalImp)(self, _cmd, center, response, + completionHandler); + } + + // Then, forward to Push. + [MSPush didReceiveRemoteNotification:response.notification.request.content.userInfo]; + if (!originalImp) { + + // No original implementation, we have to call the completion handler ourselves. + completionHandler(); + } +} + +#pragma clang diagnostic pop + +#endif + +@end diff --git a/AppCenterPush/AppCenterPush/MSPush.m b/AppCenterPush/AppCenterPush/MSPush.m index fdd6fb2fac..0ce9e6cc7a 100644 --- a/AppCenterPush/AppCenterPush/MSPush.m +++ b/AppCenterPush/AppCenterPush/MSPush.m @@ -16,6 +16,7 @@ #import "MSPushLog.h" #import "MSPushNotificationInternal.h" #import "MSPushPrivate.h" +#import "MSUserNotificationCenterDelegateForwarder.h" /** * Service storage key name. @@ -61,6 +62,8 @@ - (instancetype)init { _channelUnitConfiguration = [[MSChannelUnitConfiguration alloc] initDefaultConfigurationWithGroupId:[self groupId]]; _appDelegate = [MSPushAppDelegate new]; + // This call is used to force load the MSUserNotificationCenterDelegateForwarder class to register the swizzling. + [MSUserNotificationCenterDelegateForwarder doNothingButForceLoadTheClass]; #if TARGET_OS_OSX NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter]; @@ -170,7 +173,7 @@ - (void)applyEnabledState:(BOOL)isEnabled { name:NSApplicationDidFinishLaunchingNotification object:nil]; #endif - [MSAppDelegateForwarder addDelegate:self.appDelegate]; + [[MSAppDelegateForwarder sharedInstance] addDelegate:self.appDelegate]; if (!self.pushToken) { [self registerForRemoteNotifications]; } @@ -179,7 +182,7 @@ - (void)applyEnabledState:(BOOL)isEnabled { #if TARGET_OS_OSX [MS_NOTIFICATION_CENTER removeObserver:self name:NSApplicationDidFinishLaunchingNotification object:nil]; #endif - [MSAppDelegateForwarder removeDelegate:self.appDelegate]; + [[MSAppDelegateForwarder sharedInstance] removeDelegate:self.appDelegate]; MSLogInfo([MSPush logTag], @"Push service has been disabled."); } } @@ -220,9 +223,7 @@ - (void)registerForRemoteNotifications { MSLogVerbose([MSPush logTag], @"Push notifications authorization was denied."); } if (error) { - MSLogWarning([MSPush logTag], @"Push notifications authorization " - @"request has been finished with " - @"error: %@", + MSLogWarning([MSPush logTag], @"Push notifications authorization request has been finished with error: %@", error.localizedDescription); } }]; @@ -247,7 +248,7 @@ - (NSString *)convertTokenToString:(NSData *)token { - (void)sendPushToken:(NSString *)token { MSPushLog *log = [MSPushLog new]; log.pushToken = token; - [self.channelUnit enqueueItem:log]; + [self.channelUnit enqueueItem:log flags:MSFlagsDefault]; } #pragma mark - Register callbacks diff --git a/AppCenterPush/AppCenterPushTests/MSAppDelegateForwarderTests.m b/AppCenterPush/AppCenterPushTests/MSAppDelegateForwarderTests.m index 950580ea7c..6c9baafaa8 100644 --- a/AppCenterPush/AppCenterPushTests/MSAppDelegateForwarderTests.m +++ b/AppCenterPush/AppCenterPushTests/MSAppDelegateForwarderTests.m @@ -1,6 +1,7 @@ #import -#import "MSAppDelegateForwarderPrivate.h" +#import "MSDelegateForwarderPrivate.h" +#import "MSDelegateForwarderTestUtil.h" #import "MSPushAppDelegate.h" #import "MSTestFrameworks.h" #import "MSUtility+Application.h" @@ -8,6 +9,7 @@ @interface MSAppDelegateForwarderTest : XCTestCase @property(nonatomic) MSApplication *appMock; +@property(nonatomic) MSAppDelegateForwarder *sut; @end @@ -27,15 +29,16 @@ - (void)setUp { [super setUp]; // The app delegate forwarder is already set via the load method, reset it for testing. - [MSAppDelegateForwarder reset]; + [MSAppDelegateForwarder resetSharedInstance]; + self.sut = [MSAppDelegateForwarder sharedInstance]; // Mock app delegate. self.appMock = OCMClassMock([MSApplication class]); } - (void)tearDown { - [MSAppDelegateForwarder reset]; [super tearDown]; + [MSAppDelegateForwarder resetSharedInstance]; } - (void)testSwizzleOriginalPushDelegate { @@ -43,16 +46,16 @@ - (void)testSwizzleOriginalPushDelegate { // If // Mock a custom app delegate. id customDelegate = OCMProtocolMock(@protocol(MSCustomPushApplicationDelegate)); - [MSAppDelegateForwarder addDelegate:customDelegate]; + [self.sut addDelegate:customDelegate]; NSError *expectedError = [[NSError alloc] initWithDomain:NSItemProviderErrorDomain code:123 userInfo:@{}]; // App delegate not implementing any selector. id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL selectorToSwizzle = @selector(application:didFailToRegisterForRemoteNotificationsWithError:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock didFailToRegisterForRemoteNotificationsWithError:expectedError]; // Then @@ -66,11 +69,11 @@ - (void)testSwizzleOriginalPushDelegate { id selectorImp = ^{ wasCalled = YES; }; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock didFailToRegisterForRemoteNotificationsWithError:expectedError]; // Then @@ -81,13 +84,14 @@ - (void)testSwizzleOriginalPushDelegate { // If // App delegate implementing the selector indirectly. id originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; - originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [MSDelegateForwarderTestUtil createInstanceWithBaseClass:[originalBaseAppDelegate class] + andConformItToProtocol:nil]; wasCalled = NO; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock didFailToRegisterForRemoteNotificationsWithError:expectedError]; // Then @@ -103,13 +107,14 @@ - (void)testSwizzleOriginalPushDelegate { baseWasCalled = YES; }; originalBaseAppDelegate = [self createOriginalAppDelegateInstance]; - [self addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; - originalAppDelegate = [self createInstanceWithBaseClass:[originalBaseAppDelegate class] andConformItToProtocol:nil]; - [self addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:baseSelectorImp toInstance:originalBaseAppDelegate]; + originalAppDelegate = [MSDelegateForwarderTestUtil createInstanceWithBaseClass:[originalBaseAppDelegate class] + andConformItToProtocol:nil]; + [MSDelegateForwarderTestUtil addSelector:selectorToSwizzle implementation:selectorImp toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; [originalAppDelegate application:self.appMock didFailToRegisterForRemoteNotificationsWithError:expectedError]; // Then @@ -128,13 +133,13 @@ - (void)testSwizzleOriginalPushDelegate { // Adding a class method to a class requires its meta class. A meta class is // the superclass of a class. - [self addSelector:instancesRespondToSelector - implementation:instancesRespondToSelectorImp - toClass:object_getClass([originalAppDelegate class])]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:selectorToSwizzle]; + [MSDelegateForwarderTestUtil addSelector:instancesRespondToSelector + implementation:instancesRespondToSelectorImp + toClass:object_getClass([originalAppDelegate class])]; + [self.sut addDelegateSelectorToSwizzle:selectorToSwizzle]; // When - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // Then // Original delegate still responding to selector. @@ -153,47 +158,44 @@ - (void)testWithMultipleCustomPushDelegates { XCTestExpectation *customCalledExpectation2 = [self expectationWithDescription:@"Custom delegate 2 called."]; MSApplication *appMock = self.appMock; SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; + [self.sut addDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp - toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); id customAppDelegate1 = [self createCustomAppDelegateInstance]; id customDidRegisterForRemoteNotificationWithDeviceTokenImp1 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [customCalledExpectation1 fulfill]; }; - [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp1 - toInstance:customAppDelegate1]; + [MSDelegateForwarderTestUtil addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp1 + toInstance:customAppDelegate1]; id customAppDelegate2 = [self createCustomAppDelegateInstance]; id customDidRegisterForRemoteNotificationWithDeviceTokenImp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [customCalledExpectation2 fulfill]; }; - [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp2 - toInstance:customAppDelegate2]; - [MSAppDelegateForwarder addDelegate:customAppDelegate1]; - [MSAppDelegateForwarder addDelegate:customAppDelegate2]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp2 + toInstance:customAppDelegate2]; + [self.sut addDelegate:customAppDelegate1]; + [self.sut addDelegate:customAppDelegate2]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; // When [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; @@ -209,32 +211,30 @@ - (void)testWithRemovedCustomDidRegisterForRemoteNotificationWithDeviceTokenDele MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; + [self.sut addDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp - toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customDidRegisterForRemoteNotificationWithDeviceTokenImp = ^( __attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSData *deviceToken) { - // Then XCTFail(@"Custom delegate got called but is removed."); }; - [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp - toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - [MSAppDelegateForwarder removeDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + [self.sut removeDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; @@ -250,39 +250,37 @@ - (void)testDontForwardDidRegisterForRemoteNotificationWithDeviceTokenOnDisable MSApplication *appMock = self.appMock; XCTestExpectation *originalCalledExpectation = [self expectationWithDescription:@"Original delegate called."]; SEL originalDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; + [self.sut addDelegateSelectorToSwizzle:originalDidRegisterForRemoteNotificationWithDeviceTokenSel]; id originalAppDelegate = [self createOriginalAppDelegateInstance]; id originalDidRegisterForRemoteNotificationWithDeviceTokenImp = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSData *deviceToken) { - // Then assertThat(application, is(appMock)); assertThat(deviceToken, is(expectedToken)); [originalCalledExpectation fulfill]; }; - [self addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp - toInstance:originalAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:originalDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:originalDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:originalAppDelegate]; SEL customDidRegisterForRemoteNotificationWithDeviceTokenSel = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:); id customAppDelegate = [self createCustomAppDelegateInstance]; id customDidRegisterForRemoteNotificationWithDeviceTokenImp = ^( __attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSData *deviceToken) { - // Then XCTFail(@"Custom delegate got called but is removed."); }; - [self addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel - implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp - toInstance:customAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; - MSAppDelegateForwarder.enabled = NO; + [MSDelegateForwarderTestUtil addSelector:customDidRegisterForRemoteNotificationWithDeviceTokenSel + implementation:customDidRegisterForRemoteNotificationWithDeviceTokenImp + toInstance:customAppDelegate]; + [self.sut addDelegate:customAppDelegate]; + self.sut.enabled = NO; // When [originalAppDelegate application:self.appMock didRegisterForRemoteNotificationsWithDeviceToken:expectedToken]; // Then [self waitForExpectations:@[ originalCalledExpectation ] timeout:1]; - MSAppDelegateForwarder.enabled = YES; + self.sut.enabled = YES; } #if TARGET_OS_IOS @@ -299,7 +297,7 @@ - (void)testDidReceiveRemoteNotification { forwardedFetchResult = fetchResult; isExpectedHandlerCalled = YES; }; - NSDictionary *expectedUserInfo = @{ @"aKey" : @"aThingBehindADoor" }; + NSDictionary *expectedUserInfo = @{@"aKey" : @"aThingBehindADoor"}; MSApplication *appMock = self.appMock; XCTestExpectation *customCalledExpectation = [self expectationWithDescription:@"Custom delegate called."]; @@ -307,21 +305,21 @@ - (void)testDidReceiveRemoteNotification { id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL didReceiveRemoteNotificationSel1 = @selector(application:didReceiveRemoteNotification:); SEL didReceiveRemoteNotificationSel2 = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel1]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel2]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel1]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel2]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; id didReceiveRemoteNotificationImp1 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSDictionary *userInfo) { - // Then assertThat(application, is(appMock)); assertThat(userInfo, is(expectedUserInfo)); }; - [self addSelector:didReceiveRemoteNotificationSel1 implementation:didReceiveRemoteNotificationImp1 toInstance:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel1 + implementation:didReceiveRemoteNotificationImp1 + toInstance:customAppDelegate]; id didReceiveRemoteNotificationImp2 = ^(__attribute__((unused)) id itSelf, MSApplication *application, NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThat(application, is(appMock)); assertThat(userInfo, is(expectedUserInfo)); @@ -333,9 +331,11 @@ - (void)testDidReceiveRemoteNotification { fetchHandler(expectedFetchResult); [customCalledExpectation fulfill]; }; - [self addSelector:didReceiveRemoteNotificationSel2 implementation:didReceiveRemoteNotificationImp2 toInstance:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel2 + implementation:didReceiveRemoteNotificationImp2 + toInstance:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:appMock didReceiveRemoteNotification:expectedUserInfo]; @@ -368,29 +368,31 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerImplementedByOriginalAn id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); fetchHandler(expectedFetchResult); [originalCalledExpectation fulfill]; }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; id customDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); fetchHandler(expectedFetchResult); [customCalledExpectation fulfill]; }; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp toInstance:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp + toInstance:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -421,19 +423,20 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerImplementedByOriginalOn id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); fetchHandler(expectedFetchResult); [originalCalledExpectation fulfill]; }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -460,12 +463,12 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerImplementedByNoOne { // Setup the original delegate. id originalAppDelegate = [self createOriginalAppDelegateInstance]; SEL didReceiveRemoteNotificationSel = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:); - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -497,29 +500,31 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerTriage { id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); fetchHandler(originalFetchResult); isOriginalHandlerCalled = YES; }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; id customDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); fetchHandler(customFetchResult); isCustomHandlerCalled = YES; }; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp toInstance:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp + toInstance:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -635,7 +640,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerOriginalCalledFirst { id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -647,8 +651,10 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerOriginalCalledFirst { [originalCalledExpectation fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; @@ -657,7 +663,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerOriginalCalledFirst { __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { // Simulate a background download. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - // Then [NSThread sleepForTimeInterval:0.003]; assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -665,9 +670,11 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerOriginalCalledFirst { [customCalledExpectation fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp toInstance:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp + toInstance:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -699,7 +706,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerCustomCalledFirst { id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -711,8 +717,10 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerCustomCalledFirst { [originalCalledExpectation fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup a custom delegate. id customAppDelegate = [self createCustomAppDelegateInstance]; @@ -721,7 +729,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerCustomCalledFirst { __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { // Simulate a background download. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - // Then [NSThread sleepForTimeInterval:0.001]; assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -729,9 +736,11 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerCustomCalledFirst { [customCalledExpectation fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp toInstance:customAppDelegate]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp + toInstance:customAppDelegate]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; @@ -765,7 +774,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom id originalDidReceiveRemoteNotificationImp = ^(__attribute__((unused)) id itSelf, __attribute__((unused)) MSApplication *application, __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { - // Then assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -778,8 +786,10 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom [originalCalledExpectation fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:originalDidReceiveRemoteNotificationImp toInstance:originalAppDelegate]; - [MSAppDelegateForwarder addAppDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:originalDidReceiveRemoteNotificationImp + toInstance:originalAppDelegate]; + [self.sut addDelegateSelectorToSwizzle:didReceiveRemoteNotificationSel]; // Setup custom delegates. id customAppDelegate1 = [self createCustomAppDelegateInstance]; @@ -789,7 +799,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { // Simulate a background download. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - // Then [NSThread sleepForTimeInterval:arc4random_uniform(2) / 100]; assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -803,7 +812,6 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom __attribute__((unused)) NSDictionary *userInfo, void (^fetchHandler)(UIBackgroundFetchResult)) { // Simulate a background download. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ - // Then [NSThread sleepForTimeInterval:arc4random_uniform(2) / 100]; assertThatBool(isExpectedHandlerCalled, isFalse()); @@ -812,19 +820,22 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom [customCalledExpectation2 fulfill]; }); }; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp1 toInstance:customAppDelegate1]; - [self addSelector:didReceiveRemoteNotificationSel implementation:customDidReceiveRemoteNotificationImp2 toInstance:customAppDelegate2]; - [MSAppDelegateForwarder swizzleOriginalDelegate:originalAppDelegate]; - [MSAppDelegateForwarder addDelegate:customAppDelegate1]; - [MSAppDelegateForwarder addDelegate:customAppDelegate2]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp1 + toInstance:customAppDelegate1]; + [MSDelegateForwarderTestUtil addSelector:didReceiveRemoteNotificationSel + implementation:customDidReceiveRemoteNotificationImp2 + toInstance:customAppDelegate2]; + [self.sut swizzleOriginalDelegate:originalAppDelegate]; + [self.sut addDelegate:customAppDelegate1]; + [self.sut addDelegate:customAppDelegate2]; // When [originalAppDelegate application:self.appMock didReceiveRemoteNotification:@{} fetchCompletionHandler:expectedFetchHandler]; // Then - [self waitForExpectationsWithTimeout:1000 + [self waitForExpectationsWithTimeout:1 handler:^(__unused NSError *error) { - // In the end the completion handler must be // called with the forwarded value. if (error) { @@ -841,45 +852,14 @@ - (void)testDidReceiveRemoteNotificationCompletionHandlerAsyncWithMultipleCustom #endif -#pragma mark - Private - -- (NSString *)generateClassName { - return [@"C" stringByAppendingString:MS_UUID_STRING]; -} - -- (id)createInstanceConformingToProtocol:(Protocol *)protocol { - return [self createInstanceWithBaseClass:[NSObject class] andConformItToProtocol:protocol]; -} - -- (id)createInstanceWithBaseClass:(Class) class andConformItToProtocol:(Protocol *)protocol { - - // Generate class name to prevent conflicts in runtime added classes. - const char *name = [[self generateClassName] UTF8String]; - Class newClass = objc_allocateClassPair(class, name, 0); - if (protocol) { - class_addProtocol(newClass, protocol); - } - objc_registerClassPair(newClass); - return [newClass new]; -} +#pragma mark - Helper - - (id)createOriginalAppDelegateInstance { - return [self createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; +- (id)createOriginalAppDelegateInstance { + return [MSDelegateForwarderTestUtil createInstanceConformingToProtocol:@protocol(MSApplicationDelegate)]; } - (id)createCustomAppDelegateInstance { - return [self createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; -} - -- (void)addSelector:(SEL)selector implementation:(id)block toInstance:(id)instance { - [self addSelector:selector implementation:block toClass:[instance class]]; -} - -- (void)addSelector:(SEL)selector implementation:(id)block toClass:(id) class { - Method method = class_getInstanceMethod(class, selector); - const char *types = method_getTypeEncoding(method); - IMP imp = imp_implementationWithBlock(block); - class_addMethod(class, selector, imp, types); + return [MSDelegateForwarderTestUtil createInstanceConformingToProtocol:@protocol(MSCustomApplicationDelegate)]; } @end diff --git a/AppCenterPush/AppCenterPushTests/MSUserNotificationCenterDelegateForwarderTest.m b/AppCenterPush/AppCenterPushTests/MSUserNotificationCenterDelegateForwarderTest.m new file mode 100644 index 0000000000..a038072026 --- /dev/null +++ b/AppCenterPush/AppCenterPushTests/MSUserNotificationCenterDelegateForwarderTest.m @@ -0,0 +1,285 @@ +#import +#if !TARGET_OS_OSX +#import +#endif +#import "MSDelegateForwarderPrivate.h" +#import "MSDelegateForwarderTestUtil.h" +#import "MSPush.h" +#import "MSTestFrameworks.h" +#import "MSUserNotificationCenterDelegateForwarder.h" + +#define MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED \ + if (!NSClassFromString(@"UNUserNotificationCenter")) { \ + return; \ + } + +#if !TARGET_OS_OSX +@interface MSUserNotificationCenterDelegateForwarderTest : XCTestCase + +@property(nonatomic) MSUserNotificationCenterDelegateForwarder *sut; +@property(nonatomic) UNNotificationResponse *notificationResponseMock; +@property(nonatomic) UNUserNotificationCenter *notificationCenterMock; +@property(nonatomic) NSDictionary *expectedUserInfo; +@property(nonatomic) id pushMock; + +@end + +@implementation MSUserNotificationCenterDelegateForwarderTest + +- (void)setUp { + [super setUp]; + + // The delegate forwarder is already set via the load method, reset it for testing. + [MSUserNotificationCenterDelegateForwarder resetSharedInstance]; + self.sut = [MSUserNotificationCenterDelegateForwarder sharedInstance]; + self.pushMock = OCMClassMock([MSPush class]); + self.expectedUserInfo = @{@"aps" : @{@"alert" : @"message"}}; + + // Mock notification center and notification response. + MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED + self.notificationCenterMock = OCMClassMock([UNUserNotificationCenter class]); + UNNotificationContent *notificationContentMock = OCMClassMock([UNNotificationContent class]); + UNNotificationRequest *notificationRequestMock = OCMClassMock([UNNotificationRequest class]); + UNNotification *notificationMock = OCMClassMock([UNNotification class]); + self.notificationResponseMock = OCMClassMock([UNNotificationResponse class]); + OCMStub([notificationMock request]).andReturn(notificationRequestMock); + OCMStub([notificationRequestMock content]).andReturn(notificationContentMock); + OCMStub([notificationContentMock userInfo]).andReturn(self.expectedUserInfo); + OCMStub([self.notificationResponseMock notification]).andReturn(notificationMock); +} + +- (void)tearDown { + [super tearDown]; + [MSUserNotificationCenterDelegateForwarder resetSharedInstance]; +} + +- (void)testSetEnabledYesFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSUserNotificationCenterDelegateForwarderEnabledKey]).andReturn(@YES); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isTrue()); +} + +- (void)testSetEnabledNoFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSUserNotificationCenterDelegateForwarderEnabledKey]).andReturn(@NO); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isFalse()); +} + +- (void)testSetEnabledNoneFromPlist { + + // If + id bundleMock = OCMClassMock([NSBundle class]); + OCMStub([bundleMock objectForInfoDictionaryKey:kMSUserNotificationCenterDelegateForwarderEnabledKey]).andReturn(nil); + OCMStub([bundleMock mainBundle]).andReturn(bundleMock); + + // When + [[self.sut class] load]; + + // Then + assertThatBool(self.sut.enabled, isTrue()); +} + +- (void)testWillPresentNotificationSwizzledWhenNoOriginalImplementation { + MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED + + // If + XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"Completion handler called."]; + void (^completionHandler)(UNNotificationPresentationOptions) = ^void(UNNotificationPresentationOptions options) { + assertThatInt(options, equalToInt(UNNotificationPresentationOptionNone)); + [completionHandlerExpectation fulfill]; + }; + + // Original delegate doesn't implement anything. + id originalUserNotificationCenterDelegate = [self createOriginalUserNotificationCenterDelegateInstance]; + + // Swizzle. + [[self.sut class] load]; + [self.sut swizzleOriginalDelegate:originalUserNotificationCenterDelegate]; + + // When + [originalUserNotificationCenterDelegate userNotificationCenter:self.notificationCenterMock + willPresentNotification:self.notificationResponseMock.notification + withCompletionHandler:completionHandler]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(__unused NSError *error) { + // In the end the completion handler must be + // called with the forwarded value. + if (error) { + XCTFail(@"Failed to complete all delegate " + @"invocations with error: %@", + error.localizedDescription); + } else { + OCMVerify([self.pushMock didReceiveRemoteNotification:self.expectedUserInfo]); + } + }]; +} + +- (void)testDidReceiveNotificationResponseSwizzledWhenNoOriginalImplementation { + MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED + + // If + XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"Completion handler called."]; + void (^completionHandler)(void) = ^void() { + [completionHandlerExpectation fulfill]; + }; + + // Original delegate doesn't implement anything. + id originalUserNotificationCenterDelegate = [self createOriginalUserNotificationCenterDelegateInstance]; + + // Swizzle. + [[self.sut class] load]; + [self.sut swizzleOriginalDelegate:originalUserNotificationCenterDelegate]; + + // When + [originalUserNotificationCenterDelegate userNotificationCenter:self.notificationCenterMock + didReceiveNotificationResponse:self.notificationResponseMock + withCompletionHandler:completionHandler]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(__unused NSError *error) { + // In the end the completion handler must be + // called with the forwarded value. + if (error) { + XCTFail(@"Failed to complete all delegate " + @"invocations with error: %@", + error.localizedDescription); + } else { + OCMVerify([self.pushMock didReceiveRemoteNotification:self.expectedUserInfo]); + } + }]; +} + +- (void)testWillPresentNotificationSwizzledWhenAlsoImplementatedByOriginalDelegate { + MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED + + // If + __block short pushCallCounter; + OCMStub([self.pushMock didReceiveRemoteNotification:self.expectedUserInfo]).andDo(^(__unused NSInvocation *invocation) { + pushCallCounter++; + }); + XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"Completion handler called."]; + void (^completionHandler)(UNNotificationPresentationOptions) = ^void(UNNotificationPresentationOptions options) { + assertThatInt(options, equalToInt(UNNotificationPresentationOptionAlert)); + [completionHandlerExpectation fulfill]; + }; + + // Original delegate implements the callback. + id originalUserNotificationCenterDelegate = [self createOriginalUserNotificationCenterDelegateInstance]; + SEL willPresentNotificationSel = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:); + id originalWillPresentNotificationImp = + ^(__attribute__((unused)) id itSelf, __attribute__((unused)) UNUserNotificationCenter *notificationCenter, + __attribute__((unused)) UNNotification *notification, void (^handler)(UNNotificationPresentationOptions)) { + assertThat(notification, is(self.notificationResponseMock.notification)); + assertThat(notificationCenter, is(self.notificationCenterMock)); + + // Push callback must be called after the original implementation. + assertThatShort(pushCallCounter, equalToShort(0)); + handler(UNNotificationPresentationOptionAlert); + }; + [MSDelegateForwarderTestUtil addSelector:willPresentNotificationSel + implementation:originalWillPresentNotificationImp + toInstance:originalUserNotificationCenterDelegate]; + + // Swizzle. + [[self.sut class] load]; + [self.sut swizzleOriginalDelegate:originalUserNotificationCenterDelegate]; + + // When + [originalUserNotificationCenterDelegate userNotificationCenter:self.notificationCenterMock + willPresentNotification:self.notificationResponseMock.notification + withCompletionHandler:completionHandler]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(__unused NSError *error) { + // In the end the completion handler must be + // called with the forwarded value. + if (error) { + XCTFail(@"Failed to complete all delegate " + @"invocations with error: %@", + error.localizedDescription); + } else { + assertThatShort(pushCallCounter, equalToShort(1)); + } + }]; +} + +- (void)testDidReceiveNotificationResponseSwizzledWhenAlsoImplementatedByOriginalDelegate { + MS_RETURN_IF_USER_NOTIFICATION_CENTER_NOT_SUPPORTED + + // If + __block short pushCallCounter; + OCMStub([self.pushMock didReceiveRemoteNotification:self.expectedUserInfo]).andDo(^(__unused NSInvocation *invocation) { + pushCallCounter++; + }); + XCTestExpectation *completionHandlerExpectation = [self expectationWithDescription:@"Completion handler called."]; + void (^completionHandler)(void) = ^void() { + [completionHandlerExpectation fulfill]; + }; + + // Original delegate implements the callback. + id originalUserNotificationCenterDelegate = [self createOriginalUserNotificationCenterDelegateInstance]; + SEL didReceiveNotificationResponseSel = @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:); + id originalDidReceiveNotificationResponseImp = + ^(__attribute__((unused)) id itSelf, __attribute__((unused)) UNUserNotificationCenter *notificationCenter, + __attribute__((unused)) UNNotificationResponse *notificationResponse, void (^handler)(void)) { + assertThat(notificationResponse, is(self.notificationResponseMock)); + assertThat(notificationCenter, is(self.notificationCenterMock)); + + // Push callback must be called after the original implementation. + assertThatShort(pushCallCounter, equalToShort(0)); + handler(); + }; + [MSDelegateForwarderTestUtil addSelector:didReceiveNotificationResponseSel + implementation:originalDidReceiveNotificationResponseImp + toInstance:originalUserNotificationCenterDelegate]; + + // Swizzle. + [[self.sut class] load]; + [self.sut swizzleOriginalDelegate:originalUserNotificationCenterDelegate]; + + // When + [originalUserNotificationCenterDelegate userNotificationCenter:self.notificationCenterMock + didReceiveNotificationResponse:self.notificationResponseMock + withCompletionHandler:completionHandler]; + + // Then + [self waitForExpectationsWithTimeout:1 + handler:^(__unused NSError *error) { + // In the end the completion handler must be + // called with the forwarded value. + if (error) { + XCTFail(@"Failed to complete all delegate " + @"invocations with error: %@", + error.localizedDescription); + } else { + assertThatShort(pushCallCounter, equalToShort(1)); + } + }]; +} + +- (id)createOriginalUserNotificationCenterDelegateInstance { + return [MSDelegateForwarderTestUtil createInstanceConformingToProtocol:@protocol(UNUserNotificationCenterDelegate)]; +} + +@end +#endif diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b62ed3fb2..0bd8fafb73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # App Center SDK for iOS and macOS Change Log +## Version 1.11.0 + +### AppCenter + +* **[Fix]** Fix an issue where concurrent modification of custom properties was not thread safe. +* **[Fix]** Fix validating and discarding Not a Number (NaN) and infinite double values for custom properties. +* **[Fix]** Use standard SQL syntax to avoid affecting users with custom SQLite libraries. +* **[Fix]** Get database page size dynamically to support custom values. + +### AppCenterAnalytics + +* **[Feature]** Add new trackEvent APIs that take priority (normal or critical) of event logs. Events tracked with critical flag will take precedence over all other logs except crash logs (when AppCenterCrashes is enabled), and only be dropped if storage is full and must make room for newer critical events or crashes logs. + +### AppCenterCrashes + +* **[Fix]** Do not force crash macOS application on uncaught exception. If you need this behavior you can set the special flag yourself: + + ```objc + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions" : @YES }]; + ``` + +### AppCenterPush + +* **[Fix]** Fix `push:didReceivePushNotification:` callback not triggered on notification tapped or received in foreground when a `UNUserNotificationCenterDelegate` is set. If you have implemented this delegate please remove any call to the `MSPush#didReceiveRemoteNotification:` method as it's now handled by the new [User Notification Center Delegate Forwarder](https://docs.microsoft.com/appcenter/sdk/push/ios). + +___ + ## Version 1.10.1 This version contains a bug fix for macOS. diff --git a/Config/Tests iOS.xcconfig b/Config/Tests iOS.xcconfig index 24222f5414..f68f71ad5b 100644 --- a/Config/Tests iOS.xcconfig +++ b/Config/Tests iOS.xcconfig @@ -1,7 +1,7 @@ #include "./Tests.xcconfig" #include "./iOS.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework UIKit -ObjC -lsqlite3 -framework CrashReporter -lz +OTHER_LDFLAGS = $(inherited) -framework UIKit -ObjC -framework CrashReporter -lz LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks @loader_path/Frameworks "$(SRCROOT)/../Vendor/iOS/OCMock" "$(SRCROOT)/../Vendor/iOS/OCHamcrest" "$(SRCROOT)/../Vendor/iOS/OHHTTPStubs" diff --git a/Config/Tests macOS.xcconfig b/Config/Tests macOS.xcconfig index b9869764cd..396b2be568 100644 --- a/Config/Tests macOS.xcconfig +++ b/Config/Tests macOS.xcconfig @@ -1,7 +1,7 @@ #include "./Tests.xcconfig" #include "./macOS.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework AppKit -ObjC -lsqlite3 -lz +OTHER_LDFLAGS = $(inherited) -framework AppKit -ObjC -lz LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks @loader_path/Frameworks $(TOOLCHAIN_DIR)/usr/lib/swift/macosx "$(SRCROOT)/../Vendor/macOS/OCMock" "$(SRCROOT)/../Vendor/macOS/OCHamcrest" "$(SRCROOT)/../Vendor/macOS/OHHTTPStubs" diff --git a/Config/Tests tvOS.xcconfig b/Config/Tests tvOS.xcconfig index 7ecdc03836..3a0b5b6cb9 100644 --- a/Config/Tests tvOS.xcconfig +++ b/Config/Tests tvOS.xcconfig @@ -1,7 +1,7 @@ #include "./Tests.xcconfig" #include "./tvOS.xcconfig" -OTHER_LDFLAGS = $(inherited) -framework UIKit -ObjC -lsqlite3 -framework CrashReporter -lz +OTHER_LDFLAGS = $(inherited) -framework UIKit -ObjC -framework CrashReporter -lz LD_RUNPATH_SEARCH_PATHS = @executable_path/Frameworks @loader_path/Frameworks "$(SRCROOT)/../Vendor/tvOS/OCMock" "$(SRCROOT)/../Vendor/tvOS/OCHamcrest" "$(SRCROOT)/../Vendor/tvOS/OHHTTPStubs" diff --git a/Config/Tests.xcconfig b/Config/Tests.xcconfig index 8d5fa97251..5bc2772dd0 100644 --- a/Config/Tests.xcconfig +++ b/Config/Tests.xcconfig @@ -1,5 +1,5 @@ FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../Vendor/$(APPCENTER_BUILD_PLATFORM)"/** -HEADER_SEARCH_PATHS = "$(SRCROOT)/../Vendor/$(APPCENTER_BUILD_PLATFORM)"/** +HEADER_SEARCH_PATHS = "$(SRCROOT)/../Vendor/$(APPCENTER_BUILD_PLATFORM)"/** "$(SRCROOT)/../Vendor/SQLite3" LIBRARY_SEARCH_PATHS = "$(SRCROOT)/../Vendor/$(APPCENTER_BUILD_PLATFORM)"/** APPLICATION_EXTENSION_API_ONLY = NO diff --git a/Config/Version.xcconfig b/Config/Version.xcconfig index 6a0ebde3a8..c3382819f8 100644 --- a/Config/Version.xcconfig +++ b/Config/Version.xcconfig @@ -1,2 +1,2 @@ BUILD_NUMBER = 1 -VERSION_STRING = 1.10.1 +VERSION_STRING = 1.11.0 diff --git a/Documentation/iOS/AppCenter/.jazzy.yaml b/Documentation/iOS/AppCenter/.jazzy.yaml index d7813781a7..7704f6b589 100644 --- a/Documentation/iOS/AppCenter/.jazzy.yaml +++ b/Documentation/iOS/AppCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: AppCenter -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/AppCenterAnalytics/.jazzy.yaml b/Documentation/iOS/AppCenterAnalytics/.jazzy.yaml index 01ab3ccf52..3a75122ae3 100644 --- a/Documentation/iOS/AppCenterAnalytics/.jazzy.yaml +++ b/Documentation/iOS/AppCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: AppCenterAnalytics -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/AppCenterCrashes/.jazzy.yaml b/Documentation/iOS/AppCenterCrashes/.jazzy.yaml index c8d044f192..76bfa95540 100644 --- a/Documentation/iOS/AppCenterCrashes/.jazzy.yaml +++ b/Documentation/iOS/AppCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: AppCenterCrashes -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/AppCenterDistribute/.jazzy.yaml b/Documentation/iOS/AppCenterDistribute/.jazzy.yaml index f45130841d..9a9c22d99f 100644 --- a/Documentation/iOS/AppCenterDistribute/.jazzy.yaml +++ b/Documentation/iOS/AppCenterDistribute/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: AppCenterDistribute -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/iOS/AppCenterPush/.jazzy.yaml b/Documentation/iOS/AppCenterPush/.jazzy.yaml index 07de6f744c..512ef45ca1 100644 --- a/Documentation/iOS/AppCenterPush/.jazzy.yaml +++ b/Documentation/iOS/AppCenterPush/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: iphonesimulator theme: ../../Themes/apple module: AppCenterPush -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/AppCenter/.jazzy.yaml b/Documentation/macOS/AppCenter/.jazzy.yaml index ceb1652e86..dd5eac3035 100644 --- a/Documentation/macOS/AppCenter/.jazzy.yaml +++ b/Documentation/macOS/AppCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: AppCenter -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/AppCenterAnalytics/.jazzy.yaml b/Documentation/macOS/AppCenterAnalytics/.jazzy.yaml index 2af00b809a..ca866e16d1 100644 --- a/Documentation/macOS/AppCenterAnalytics/.jazzy.yaml +++ b/Documentation/macOS/AppCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: AppCenterAnalytics -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/AppCenterCrashes/.jazzy.yaml b/Documentation/macOS/AppCenterCrashes/.jazzy.yaml index d3e089f9bc..466bf9da93 100644 --- a/Documentation/macOS/AppCenterCrashes/.jazzy.yaml +++ b/Documentation/macOS/AppCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: AppCenterCrashes -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/macOS/AppCenterPush/.jazzy.yaml b/Documentation/macOS/AppCenterPush/.jazzy.yaml index f76102d6b8..710b28c6c8 100644 --- a/Documentation/macOS/AppCenterPush/.jazzy.yaml +++ b/Documentation/macOS/AppCenterPush/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: macosx theme: ../../Themes/apple module: AppCenterPush -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/AppCenter/.jazzy.yaml b/Documentation/tvOS/AppCenter/.jazzy.yaml index 06c2ccc526..6075541cc8 100644 --- a/Documentation/tvOS/AppCenter/.jazzy.yaml +++ b/Documentation/tvOS/AppCenter/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: AppCenter -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/AppCenterAnalytics/.jazzy.yaml b/Documentation/tvOS/AppCenterAnalytics/.jazzy.yaml index 2d47594f63..5fe48ac7e2 100644 --- a/Documentation/tvOS/AppCenterAnalytics/.jazzy.yaml +++ b/Documentation/tvOS/AppCenterAnalytics/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: AppCenterAnalytics -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/Documentation/tvOS/AppCenterCrashes/.jazzy.yaml b/Documentation/tvOS/AppCenterCrashes/.jazzy.yaml index 4ba471098d..73f0a24c77 100644 --- a/Documentation/tvOS/AppCenterCrashes/.jazzy.yaml +++ b/Documentation/tvOS/AppCenterCrashes/.jazzy.yaml @@ -5,7 +5,7 @@ sdk: appletvsimulator theme: ../../Themes/apple module: AppCenterCrashes -module_version: 1.10.1 +module_version: 1.11.0 author: Microsoft Corp author_url: http://www.microsoft.com diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..63725b2cf1 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ + + +Things to consider before you submit the PR: + +* [ ] Has `CHANGELOG.md` been updated? +* [ ] Are tests passing locally? +* [ ] Are the files formatted correctly? +* [ ] Did you add unit tests? +* [ ] Did you test your change with either the sample apps that are included in the repository or with a blank app that uses your change? + +## Description + +A few sentences describing the overall goals of the pull request. + +## Related PRs or issues + +List related PRs and other issues. + +## Misc + +Add what's missing, notes on what you tested, additional thoughts or questions. diff --git a/README.md b/README.md index 4ae7e2d09a..363390881a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ You must sign a [Contributor License Agreement](https://cla.microsoft.com/) befo ### 2.3 Code Formatting -All Objective-C files follow LLVM coding style (with a few exceptions) and are formatted accordingly. If you use Xcode for your contribution, use our [.clang_format](https://github.com/Microsoft/AppCenter-SDK-Apple/blob/develop/.clang-format) file to make sure files have the correct code format before submitting PRs. You can find a clang formatting tool [here](https://github.com/mapbox/XcodeClangFormat) if you don't have a tool for formatting code. +All Objective-C files follow LLVM coding style (with a few exceptions) and are formatted accordingly. To format your changes, make sure you have the `clang-format` tool. It can be installed with [Homebrew](https://brew.sh) using the command `brew install clang-format`. Once you have installed `clang-format`, run `./clang-format-changed-files.sh` from the repository root - this will format all files that have changes against the remote `develop` branch (it will also perform a `git fetch`). ## 3. Contact diff --git a/Sasquatch/Config/Puppet.xcconfig b/Sasquatch/Config/Puppet.xcconfig index 5a42a4b3ef..47d65d30ba 100644 --- a/Sasquatch/Config/Puppet.xcconfig +++ b/Sasquatch/Config/Puppet.xcconfig @@ -1,6 +1,6 @@ ASSETCATALOG_COMPILER_APPICON_NAME = PuppetIcon -GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GCC_PREPROCESSOR_MACRO_PUPPET +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) GCC_PREPROCESSOR_MACRO_PUPPET SQLITE_DEFAULT_PAGE_SIZE=1024 SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) ACTIVE_COMPILATION_CONDITION_PUPPET USER_HEADER_SEARCH_PATHS = $(SRCROOT)/../AppCenter/AppCenter/** $(SRCROOT)/../AppCenterAnalytics/AppCenterAnalytics/** $(SRCROOT)/../AppCenterCrashes/AppCenterCrashes/** $(SRCROOT)/../AppCenterDistribute/AppCenterDistribute/** $(SRCROOT)/../AppCenterPush/AppCenterPush/** diff --git a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj index f10797942c..ccde01227d 100644 --- a/Sasquatch/Sasquatch.xcodeproj/project.pbxproj +++ b/Sasquatch/Sasquatch.xcodeproj/project.pbxproj @@ -98,7 +98,6 @@ B20AE43E210281F800A1A195 /* libAppCenterCrashes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE4022102818B00A1A195 /* libAppCenterCrashes.a */; }; B20AE43F210281FF00A1A195 /* libAppCenterDistribute.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE4162102819C00A1A195 /* libAppCenterDistribute.a */; }; B20AE4402102820300A1A195 /* libAppCenterPush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE435210281C400A1A195 /* libAppCenterPush.a */; }; - B20AE48621028E2A00A1A195 /* libsqlite3.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE48521028E2A00A1A195 /* libsqlite3.tbd */; }; B20AE48821028E3300A1A195 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE48721028E3300A1A195 /* libc++.tbd */; }; B20AE48A21028E3C00A1A195 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B20AE48921028E3C00A1A195 /* libz.tbd */; }; B23DF4FF2109382200F1CAD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84226C1C1E88FEFC00798417 /* Assets.xcassets */; }; @@ -225,6 +224,7 @@ D31EF9721E9517C600450162 /* SasquatchWatchSwift.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = D31EF9541E9517C500450162 /* SasquatchWatchSwift.app */; }; F809D5041FC6DA2B005E0F83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F809D5021FC6DA2B005E0F83 /* LaunchScreen.storyboard */; }; F809D5051FC6DD80005E0F83 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F809D5021FC6DA2B005E0F83 /* LaunchScreen.storyboard */; }; + F8669FB82195B6A2003933A8 /* sqlite3.c in Sources */ = {isa = PBXBuildFile; fileRef = F8669FB72195B6A2003933A8 /* sqlite3.c */; settings = {COMPILER_FLAGS = "-w"; }; }; F89D79091E93DA3A0094521F /* CrashLibIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; }; F89D790A1E93DA3A0094521F /* CrashLibIOS.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F89D790C1E93DA500094521F /* CrashLibIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F89D79081E93DA3A0094521F /* CrashLibIOS.framework */; }; @@ -763,6 +763,7 @@ F849248A2112FA3400E14DBE /* Watch Extension.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Watch Extension.xcconfig"; sourceTree = ""; }; F84BA0D6211342620020F13B /* Puppet UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Puppet UITests.xcconfig"; sourceTree = ""; }; F84BA102211342620020F13B /* UITests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = UITests.xcconfig; sourceTree = ""; }; + F8669FB72195B6A2003933A8 /* sqlite3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = sqlite3.c; path = ../Vendor/SQLite3/sqlite3.c; sourceTree = ""; }; F89D79081E93DA3A0094521F /* CrashLibIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CrashLibIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F8A502252149339100872B3B /* MSEnumPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MSEnumPicker.swift; sourceTree = ""; }; F8AA9F052164F91B009104E3 /* MSAnalyticsTypedPropertyTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MSAnalyticsTypedPropertyTableViewCell.xib; sourceTree = ""; }; @@ -817,7 +818,6 @@ B20AE48A21028E3C00A1A195 /* libz.tbd in Frameworks */, B20AE48821028E3300A1A195 /* libc++.tbd in Frameworks */, B2A888A62102A44B001ACFDF /* SafariServices.framework in Frameworks */, - B20AE48621028E2A00A1A195 /* libsqlite3.tbd in Frameworks */, B20AE39521027D6200A1A195 /* CrashLibIOS.framework in Frameworks */, B20AE4402102820300A1A195 /* libAppCenterPush.a in Frameworks */, F8FE732F210730A8005C6A74 /* CrashReporter.framework in Frameworks */, @@ -1247,6 +1247,7 @@ F8FE732D21073042005C6A74 /* Vendor */ = { isa = PBXGroup; children = ( + F8669FB72195B6A2003933A8 /* sqlite3.c */, F8FE732E210730A8005C6A74 /* CrashReporter.framework */, ); name = Vendor; @@ -2178,6 +2179,7 @@ B2E011F721091EF6003F4A5B /* MSAnalyticsPropertyTableViewCell.swift in Sources */, B20AE38121027D6200A1A195 /* MSTransmissionTargets.swift in Sources */, B2586C87210A47A800664A43 /* MSAnalyticsResult.swift in Sources */, + F8669FB82195B6A2003933A8 /* sqlite3.c in Sources */, B2E011FB21091F04003F4A5B /* MSAnalyticsTransmissionTargetSelectorViewCell.swift in Sources */, B20AE38321027D6200A1A195 /* MSEventFilter.m in Sources */, B20AE38521027D6200A1A195 /* TargetPropertiesTableSection.swift in Sources */, diff --git a/Sasquatch/Sasquatch/AppCenterDelegate.swift b/Sasquatch/Sasquatch/AppCenterDelegate.swift index 0e78b55f4b..7dd7195de7 100644 --- a/Sasquatch/Sasquatch/AppCenterDelegate.swift +++ b/Sasquatch/Sasquatch/AppCenterDelegate.swift @@ -33,7 +33,9 @@ import AppCenter // MSAnalytics section. func trackEvent(_ eventName: String) func trackEvent(_ eventName: String, withProperties: Dictionary) + func trackEvent(_ eventName: String, withProperties: Dictionary, flags: MSFlags) func trackEvent(_ eventName: String, withTypedProperties: MSEventProperties) + func trackEvent(_ eventName: String, withTypedProperties: MSEventProperties?, flags: MSFlags) func trackPage(_ pageName: String) func trackPage(_ pageName: String, withProperties: Dictionary) func resume() diff --git a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard index 7471a23b90..c351753686 100644 --- a/Sasquatch/Sasquatch/Base.lproj/Main.storyboard +++ b/Sasquatch/Sasquatch/Base.lproj/Main.storyboard @@ -1,12 +1,11 @@ - + - - + @@ -29,14 +28,14 @@ - + @@ -52,7 +51,7 @@ - +