diff --git a/.gitignore b/.gitignore index 82d4e525a..1bdeeaf0b 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ frameworks/ # CBL files: Tests/Extensions/CouchbaseLiteVectorSearch.xcframework +Tests/Extensions/.downloaded-version Tests/Extensions/LICENSE.txt Tests/Extensions/Build vendor/couchbase-lite-core-EE @@ -28,4 +29,5 @@ vendor/couchbase-lite-core-EE .idea # Agents -settings.local.json \ No newline at end of file +settings.local.json +BinaryTests/ diff --git a/CouchbaseLite.xcodeproj/project.pbxproj b/CouchbaseLite.xcodeproj/project.pbxproj index b0cee97cd..95457725a 100644 --- a/CouchbaseLite.xcodeproj/project.pbxproj +++ b/CouchbaseLite.xcodeproj/project.pbxproj @@ -236,12 +236,6 @@ 1AC7546F2897ADA0006CF48F /* ReplicatorTest+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC754622897A9C9006CF48F /* ReplicatorTest+Collection.swift */; }; 1AC754702897ADA2006CF48F /* ReplicatorTest+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC754622897A9C9006CF48F /* ReplicatorTest+Collection.swift */; }; 1AC754712897ADA3006CF48F /* ReplicatorTest+Collection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AC754622897A9C9006CF48F /* ReplicatorTest+Collection.swift */; }; - 1AC7EC29249DA24E00978C2E /* Foundation+CBL.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AC7EC27249DA24E00978C2E /* Foundation+CBL.h */; }; - 1AC7EC2A249DA24E00978C2E /* Foundation+CBL.h in Headers */ = {isa = PBXBuildFile; fileRef = 1AC7EC27249DA24E00978C2E /* Foundation+CBL.h */; }; - 1AC7EC2B249DA24E00978C2E /* Foundation+CBL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */; }; - 1AC7EC2D249DA24E00978C2E /* Foundation+CBL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */; }; - 1AC7EC3A249DA70E00978C2E /* Foundation+CBL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */; }; - 1AC7EC3B249DA70F00978C2E /* Foundation+CBL.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */; }; 1AC83BC821C026D100792098 /* DateTimeQueryFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */; }; 1ACAB8C7266723AE00B4F8E5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACAB8C6266723AE00B4F8E5 /* main.m */; }; 1ACAB8C8266723AE00B4F8E5 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACAB8C6266723AE00B4F8E5 /* main.m */; }; @@ -423,6 +417,104 @@ 40AA729E2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */; }; 40C5FD5B2B9947B3004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 40C5FD5C2B9947B9004BFD3B /* CBLVectorIndexTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 40C5FD5A2B9946E6004BFD3B /* CBLVectorIndexTypes.h */; settings = {ATTRIBUTES = (Private, ); }; }; + 40CC4B9F302C449D0002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4BA0302C449D0002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4BA1302C44CB0002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4BA2302C44CB0002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4BA3302C44CB0002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4BA4302C48240002386D /* CBLTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C5961E25B473001BB196 /* CBLTestCase.m */; }; + 40CC4BA5302C48480002386D /* ArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DD9BA71EB419BB00E502A2 /* ArrayTest.m */; }; + 40CC4BA6302C48530002386D /* DictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9352945E1E51708E005CE4E8 /* DictionaryTest.m */; }; + 40CC4BA7302C48530002386D /* FragmentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 931C14691EAAF08C0094F9B2 /* FragmentTest.m */; }; + 40CC4BA8302C49900002386D /* Support in Resources */ = {isa = PBXBuildFile; fileRef = 93DECF3E200DBE5800F44953 /* Support */; }; + 40CC4BA9302C4BE10002386D /* DocumentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C59D1E269F14001BB196 /* DocumentTest.m */; }; + 40CC4BAA302CF2630002386D /* QueryTest+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6371226A8B060016754C /* QueryTest+Meta.m */; }; + 40CC4BAB302CF2630002386D /* CollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC16CE6287D4D820041728F /* CollectionTest.m */; }; + 40CC4BAC302CF2630002386D /* QueryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9332082B1E774419000D9993 /* QueryTest.m */; }; + 40CC4BAD302CF2630002386D /* QueryTest+Join.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6382226A8DBF0016754C /* QueryTest+Join.m */; }; + 40CC4BAE302CF2630002386D /* QueryTest+Collection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A621D6C2887DCE70017F905 /* QueryTest+Collection.m */; }; + 40CC4BAF302CF2630002386D /* NotificationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 72A87A0E1E32E858008466FF /* NotificationTest.m */; }; + 40CC4BB0302CF2630002386D /* DateTimeQueryFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */; }; + 40CC4BB1302CF2630002386D /* PredictiveQueryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 93EB25CB21CDD12A0006FB88 /* PredictiveQueryTest.m */; }; + 40CC4BB3302CF2900002386D /* MigrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 938B3701200D7D1D004485D8 /* MigrationTest.m */; }; + 40CC4BB4302CF2CD0002386D /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + 40CC4BB5302CF6250002386D /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4BD11E1EF19000F90659 /* CollectionUtils.m */; }; + 40CC4BB6302CF68F0002386D /* ConcurrentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9385F3021FC645AE00032037 /* ConcurrentTest.m */; }; + 40CC4BB7302CF68F0002386D /* DatabaseEncryptionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9369A6AC207DD0ED009B5B83 /* DatabaseEncryptionTest.m */; }; + 40CC4BB8302CF77C0002386D /* DatabaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C5901E25B3F0001BB196 /* DatabaseTest.m */; }; + 40CC4BC2302D40A00002386D /* ReplicatorTest+Backgrounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4BBF302D40A00002386D /* ReplicatorTest+Backgrounding.m */; }; + 40CC4BC3302D40A00002386D /* ReplicatorTest+Backgrounding.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4BBF302D40A00002386D /* ReplicatorTest+Backgrounding.m */; }; + 40CC4BC4302D41A50002386D /* ReplicatorTest+Collection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A908400288027EE006B1885 /* ReplicatorTest+Collection.m */; }; + 40CC4BC5302D41A50002386D /* ReplicatorTest+PendingDocIds.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACDD8C223FF5BB200AF5D56 /* ReplicatorTest+PendingDocIds.m */; }; + 40CC4BC6302D41A50002386D /* ReplicatorTest+MessageEndPoint.m in Sources */ = {isa = PBXBuildFile; fileRef = 93F714202490971600624296 /* ReplicatorTest+MessageEndPoint.m */; }; + 40CC4BC7302D41A50002386D /* ReplicatorTest+Main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A4160D922836C7F0061A567 /* ReplicatorTest+Main.m */; }; + 40CC4BC8302D41A50002386D /* ReplicatorTest+CustomConflict.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A4160C52283673E0061A567 /* ReplicatorTest+CustomConflict.m */; }; + 40CC4BC9302D41A50002386D /* ReplicatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E35A811E8B3B3A00E103F9 /* ReplicatorTest.m */; }; + 40CC4BCA302D41C80002386D /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; + 40CC4BCB302D41C80002386D /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; + 40CC4BCC302D41C80002386D /* CBLMockConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 930C7F7F20FE4F7400C74A12 /* CBLMockConnection.m */; }; + 40CC4BCD302D41C80002386D /* CBLMockConnectionErrorLogic.m in Sources */ = {isa = PBXBuildFile; fileRef = 930C7F7E20FE4F7400C74A12 /* CBLMockConnectionErrorLogic.m */; }; + 40CC4BCE302D4D7C0002386D /* URLEndpointListenerTest+Collection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A9617F2289BF3C10037E78E /* URLEndpointListenerTest+Collection.m */; }; + 40CC4BCF302D4D7C0002386D /* URLEndpointListenerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A6F0941246C78FC0097D8B5 /* URLEndpointListenerTest.m */; }; + 40CC4BD0302D4D7C0002386D /* URLEndpointListenerTest+Main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A13DD3F28B881BF00BC1084 /* URLEndpointListenerTest+Main.m */; }; + 40CC4BD1302D4DC50002386D /* AuthenticatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADA05382240218F0068F745 /* AuthenticatorTest.m */; }; + 40CC4BD2302D4DC50002386D /* TLSIdentityTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 934EF80B2453770E0053A47C /* TLSIdentityTest.m */; }; + 40CC4BD3302D632C0002386D /* AuthenticatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADA05382240218F0068F745 /* AuthenticatorTest.m */; }; + 40CC4BD4302D632C0002386D /* DateTimeQueryFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */; }; + 40CC4BD5302D632C0002386D /* MigrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 938B3701200D7D1D004485D8 /* MigrationTest.m */; }; + 40CC4BD6302D632C0002386D /* MiscCppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A4FE769225ED344009D5F43 /* MiscCppTest.mm */; }; + 40CC4BD7302D634D0002386D /* DateTimeQueryFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */; }; + 40CC4BD8302D634D0002386D /* MigrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 938B3701200D7D1D004485D8 /* MigrationTest.m */; }; + 40CC4BD9302D634D0002386D /* AuthenticatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADA05382240218F0068F745 /* AuthenticatorTest.m */; }; + 40CC4BDA302D634D0002386D /* MiscCppTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A4FE769225ED344009D5F43 /* MiscCppTest.mm */; }; + 40CC4BDB302D77CA0002386D /* DocumentExpirationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9388CBAA21BD916F005CA66D /* DocumentExpirationTest.m */; }; + 40CC4BDC302D7AC00002386D /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; + 40CC4BDD302D860D0002386D /* Test_Assertions.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4C6F1E1EFAF600F90659 /* Test_Assertions.m */; }; + 40CC4BDE302D86290002386D /* VectorSearchTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */; }; + 40CC4BDF302D86290002386D /* VectorSearchTest+Lazy.m in Sources */ = {isa = PBXBuildFile; fileRef = 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */; }; + 40CC4BE0302D86400002386D /* CBLWordEmbeddingModel.m in Sources */ = {isa = PBXBuildFile; fileRef = AE006DD32B7B9FEF00884E2B /* CBLWordEmbeddingModel.m */; }; + 40CC4C12302D87810002386D /* CouchbaseLite.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */; }; + 40CC4C13302D87810002386D /* CouchbaseLite.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 40CC4C15302D87A50002386D /* CouchbaseLiteVectorSearch.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE3302D86A70002386D /* CouchbaseLiteVectorSearch.xcframework */; }; + 40CC4C16302D87A50002386D /* CouchbaseLiteVectorSearch.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE3302D86A70002386D /* CouchbaseLiteVectorSearch.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 40CC4C373033BF4D0002386D /* PredictiveQueryTest+CoreML.m in Sources */ = {isa = PBXBuildFile; fileRef = 933F840C220BA4080093EC88 /* PredictiveQueryTest+CoreML.m */; }; + 40CC4C493033BF560002386D /* QueryTest+Main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AF555C322946ED90077DF6D /* QueryTest+Main.m */; }; + 40CC4C6A3033CF280002386D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 936483AC1E4431C6008D08B3 /* AppDelegate.m */; }; + 40CC4C6B3033CF300002386D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 936483AD1E4431C6008D08B3 /* Assets.xcassets */; }; + 40CC4C6C3033CF3F0002386D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 936483B01E4431C6008D08B3 /* Main.storyboard */; }; + 40CC4C6D3033CF3F0002386D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 936483AE1E4431C6008D08B3 /* LaunchScreen.storyboard */; }; + 40CC4C6E3033CF510002386D /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 936483B61E4431C6008D08B3 /* ViewController.m */; }; + 40CC4C6F3033CF510002386D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 936483B41E4431C6008D08B3 /* main.m */; }; + 40CC4D00303403F30002386D /* QueryTest+Main.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AF555C322946ED90077DF6D /* QueryTest+Main.m */; }; + 40CC4D01303403F30002386D /* CBLTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C5961E25B473001BB196 /* CBLTestCase.m */; }; + 40CC4D05303403F30002386D /* QueryTest+Meta.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6371226A8B060016754C /* QueryTest+Meta.m */; }; + 40CC4D07303403F30002386D /* CollectionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC16CE6287D4D820041728F /* CollectionTest.m */; }; + 40CC4D08303403F30002386D /* DatabaseTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C5901E25B3F0001BB196 /* DatabaseTest.m */; }; + 40CC4D0A303403F30002386D /* ReplicatorTest+PendingDocIds.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ACDD8C223FF5BB200AF5D56 /* ReplicatorTest+PendingDocIds.m */; }; + 40CC4D10303403F30002386D /* ReplicatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 27E35A811E8B3B3A00E103F9 /* ReplicatorTest.m */; }; + 40CC4D11303403F30002386D /* UnnestArrayIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */; }; + 40CC4D12303403F30002386D /* QueryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9332082B1E774419000D9993 /* QueryTest.m */; }; + 40CC4D13303403F30002386D /* QueryTest+Join.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AAF6382226A8DBF0016754C /* QueryTest+Join.m */; }; + 40CC4D14303403F30002386D /* DocumentExpirationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9388CBAA21BD916F005CA66D /* DocumentExpirationTest.m */; }; + 40CC4D15303403F30002386D /* QueryTest+Collection.m in Sources */ = {isa = PBXBuildFile; fileRef = 1A621D6C2887DCE70017F905 /* QueryTest+Collection.m */; }; + 40CC4D16303403F30002386D /* NotificationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 72A87A0E1E32E858008466FF /* NotificationTest.m */; }; + 40CC4D17303403F30002386D /* DateTimeQueryFunctionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */; }; + 40CC4D19303403F30002386D /* PartialIndexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */; }; + 40CC4D1A303403F30002386D /* ArrayTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 93DD9BA71EB419BB00E502A2 /* ArrayTest.m */; }; + 40CC4D1B303403F30002386D /* DocumentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9378C59D1E269F14001BB196 /* DocumentTest.m */; }; + 40CC4D1C303403F30002386D /* CollectionUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4BD11E1EF19000F90659 /* CollectionUtils.m */; }; + 40CC4D1D303403F30002386D /* CBLBlockConflictResolver.m in Sources */ = {isa = PBXBuildFile; fileRef = 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */; }; + 40CC4D1E303403F30002386D /* AuthenticatorTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 1ADA05382240218F0068F745 /* AuthenticatorTest.m */; }; + 40CC4D1F303403F30002386D /* Test_Assertions.m in Sources */ = {isa = PBXBuildFile; fileRef = 934F4C6F1E1EFAF600F90659 /* Test_Assertions.m */; }; + 40CC4D21303403F30002386D /* CBLTestCustomLogSink.m in Sources */ = {isa = PBXBuildFile; fileRef = 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */; }; + 40CC4D24303403F30002386D /* DictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9352945E1E51708E005CE4E8 /* DictionaryTest.m */; }; + 40CC4D25303403F30002386D /* FragmentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 931C14691EAAF08C0094F9B2 /* FragmentTest.m */; }; + 40CC4D27303403F30002386D /* ConcurrentTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9385F3021FC645AE00032037 /* ConcurrentTest.m */; }; + 40CC4D29303403F30002386D /* MigrationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 938B3701200D7D1D004485D8 /* MigrationTest.m */; }; + 40CC4D2A303403F30002386D /* CBLJSONUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */; }; + 40CC4D2D303403F30002386D /* CouchbaseLite.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */; }; + 40CC4D2F303403F30002386D /* Support in Resources */ = {isa = PBXBuildFile; fileRef = 93DECF3E200DBE5800F44953 /* Support */; }; + 40CC4D32303403F30002386D /* CouchbaseLite.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 40D6BCB02DDD175200F209D7 /* CBLMultipeerCertificateAuthenticator.h in Headers */ = {isa = PBXBuildFile; fileRef = 40E46AD92DD6A4E7007E495D /* CBLMultipeerCertificateAuthenticator.h */; settings = {ATTRIBUTES = (Private, ); }; }; 40D6BCB12DDD175E00F209D7 /* CBLMultipeerCertificateAuthenticator.m in Sources */ = {isa = PBXBuildFile; fileRef = 40E46ADA2DD6A4E7007E495D /* CBLMultipeerCertificateAuthenticator.m */; }; 40D6BCB22DDD176700F209D7 /* CBLPeerInfo.mm in Sources */ = {isa = PBXBuildFile; fileRef = 40E46AE62DD6A4E7007E495D /* CBLPeerInfo.mm */; }; @@ -2218,6 +2310,29 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 40CC4C14302D87810002386D /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 40CC4C16302D87A50002386D /* CouchbaseLiteVectorSearch.xcframework in Embed Frameworks */, + 40CC4C13302D87810002386D /* CouchbaseLite.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4D30303403F30002386D /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 40CC4D32303403F30002386D /* CouchbaseLite.xcframework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; 93095B0A246BC325005633B4 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -2357,8 +2472,6 @@ 1ABA63B22881A93C005835E7 /* CBLCollectionConfiguration+Internal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CBLCollectionConfiguration+Internal.h"; sourceTree = ""; }; 1AC16CE6287D4D820041728F /* CollectionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CollectionTest.m; sourceTree = ""; }; 1AC754622897A9C9006CF48F /* ReplicatorTest+Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReplicatorTest+Collection.swift"; sourceTree = ""; }; - 1AC7EC27249DA24E00978C2E /* Foundation+CBL.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Foundation+CBL.h"; sourceTree = ""; }; - 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "Foundation+CBL.mm"; sourceTree = ""; }; 1AC83BC721C026D100792098 /* DateTimeQueryFunctionTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DateTimeQueryFunctionTest.m; sourceTree = ""; }; 1ACAB8C6266723AE00B4F8E5 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 1ACDD8C223FF5BB200AF5D56 /* ReplicatorTest+PendingDocIds.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ReplicatorTest+PendingDocIds.m"; sourceTree = ""; }; @@ -2464,6 +2577,18 @@ 40CC4B76302BE6D30002386D /* CouchbaseLiteSwift-EE.private.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "CouchbaseLiteSwift-EE.private.txt"; sourceTree = ""; }; 40CC4B77302BE6E40002386D /* CouchbaseLiteSwift.private.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CouchbaseLiteSwift.private.txt; sourceTree = ""; }; 40CC4B79302BF1620002386D /* CouchbaseLiteSwift-EE.private.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = "CouchbaseLiteSwift-EE.private.modulemap"; sourceTree = ""; }; + 40CC4B80302C2FF90002386D /* CouchbaseLiteBinaryTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CouchbaseLiteBinaryTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CBL_EE_ObjC_Binary_Tests.xcconfig; sourceTree = ""; }; + 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CBL_ObjC_Binary_Tests.xcconfig; sourceTree = ""; }; + 40CC4B9D302C449D0002386D /* CBLJSONUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLJSONUtil.h; sourceTree = ""; }; + 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CBLJSONUtil.m; sourceTree = ""; }; + 40CC4BBE302D392D0002386D /* CBLTestCommon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CBLTestCommon.h; sourceTree = ""; }; + 40CC4BBF302D40A00002386D /* ReplicatorTest+Backgrounding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ReplicatorTest+Backgrounding.m"; sourceTree = ""; }; + 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = CouchbaseLite.xcframework; path = BinaryTests/Frameworks/CouchbaseLite.xcframework; sourceTree = ""; }; + 40CC4BE3302D86A70002386D /* CouchbaseLiteVectorSearch.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = CouchbaseLiteVectorSearch.xcframework; path = BinaryTests/Frameworks/CouchbaseLiteVectorSearch.xcframework; sourceTree = ""; }; + 40CC4C4E3033CE540002386D /* CBL_Binary_Tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CBL_Binary_Tests.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = CBL_ObjC_Binary_Tests_iOS_App.xcconfig; sourceTree = ""; }; + 40CC4D38303403F30002386D /* CouchbaseLiteBinaryTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CouchbaseLiteBinaryTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40D6BCBF2DDD183000F209D7 /* MultipeerReplicatorConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerReplicatorConfiguration.swift; sourceTree = ""; }; 40D6BCC12DDD191C00F209D7 /* MultipeerCertificateAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultipeerCertificateAuthenticator.swift; sourceTree = ""; }; 40D6BCC32DDD19D900F209D7 /* PeerID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeerID.swift; sourceTree = ""; }; @@ -3045,6 +3170,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 40CC4B7D302C2FF90002386D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4C15302D87A50002386D /* CouchbaseLiteVectorSearch.xcframework in Frameworks */, + 40CC4C12302D87810002386D /* CouchbaseLite.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4C4B3033CE540002386D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4D2B303403F30002386D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4D2D303403F30002386D /* CouchbaseLite.xcframework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 930B63CD246B9CD1006D94FF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -3327,6 +3476,8 @@ 27EF6AF01E32D12C004748DF /* Frameworks */ = { isa = PBXGroup; children = ( + 40CC4BE3302D86A70002386D /* CouchbaseLiteVectorSearch.xcframework */, + 40CC4BE1302D86A30002386D /* CouchbaseLite.xcframework */, 40815FB32F1058C3004D8590 /* CoreBluetooth.framework */, 93A4F2352221058500257423 /* CoreImage.framework */, 9399E4B21E932F4700B57600 /* libz.tbd */, @@ -3818,6 +3969,8 @@ 40086B522B803B2B00DA6770 /* CBLBlockConflictResolver.m */, 40938A4F2D4B464500691393 /* CBLTestCustomLogSink.h */, 40938A502D4B464500691393 /* CBLTestCustomLogSink.m */, + 40CC4B9D302C449D0002386D /* CBLJSONUtil.h */, + 40CC4B9E302C449D0002386D /* CBLJSONUtil.m */, ); path = Util; sourceTree = ""; @@ -4025,6 +4178,7 @@ 9378C5D21E26CC46001BB196 /* CBL_ObjC_Tests.xcconfig */, 9344A3621E44517B0091F581 /* CBL_ObjC_Tests_iOS.xcconfig */, 9344A3611E44517B0091F581 /* CBL_ObjC_Tests_iOS_App.xcconfig */, + 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */, 930B63FF246BA5F8006D94FF /* CBL_Swift_Tests.xcconfig */, 93BB1C7A246BAFBB004FFA00 /* CBL_Swift_Tests_iOS.xcconfig */, 93BB1C7B246BB145004FFA00 /* CBL_Swift_Tests_iOS_App.xcconfig */, @@ -4034,6 +4188,8 @@ 4006AB4A2B9048300036E66D /* CBL_EE_ObjC_Tests.xcconfig */, 932C69DD2453636100C382FB /* CBL_EE_ObjC_Tests_iOS.xcconfig */, 932C69DC2453636100C382FB /* CBL_EE_ObjC_Tests_iOS_App.xcconfig */, + 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */, + 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */, 4006AB4C2B9048930036E66D /* CBL_EE_Swift_Tests.xcconfig */, 93BB1C79246BAF89004FFA00 /* CBL_EE_Swift_Tests_iOS.xcconfig */, 93BB1C7C246BB151004FFA00 /* CBL_EE_Swift_Tests_iOS_App.xcconfig */, @@ -4072,8 +4228,6 @@ 9385F2C81FC5FF4D00032037 /* CBLLock.h */, 932EA5692061FF7D00EDB667 /* CBLVersion.h */, 932EA5582061FF7D00EDB667 /* CBLVersion.m */, - 1AC7EC27249DA24E00978C2E /* Foundation+CBL.h */, - 1AC7EC28249DA24E00978C2E /* Foundation+CBL.mm */, 1A3BA951272A3D3C002EAB2E /* CBLLockable.h */, 1ACAB8C6266723AE00B4F8E5 /* main.m */, ); @@ -4322,6 +4476,9 @@ 93BB1C89246BB1DB004FFA00 /* CBL_Swift_Tests_iOS.xctest */, 93BB1C98246BB1F1004FFA00 /* CBL_Swift_Tests.app */, 1A0BFA3627B51FD700BA84E5 /* CBL_ObjC_SG_Tests.xctest */, + 40CC4B80302C2FF90002386D /* CouchbaseLiteBinaryTests.xctest */, + 40CC4C4E3033CE540002386D /* CBL_Binary_Tests.app */, + 40CC4D38303403F30002386D /* CouchbaseLiteBinaryTests.xctest */, ); name = Products; sourceTree = ""; @@ -4407,6 +4564,7 @@ 1ADA05382240218F0068F745 /* AuthenticatorTest.m */, 9378C5951E25B473001BB196 /* CBLTestCase.h */, 9378C5961E25B473001BB196 /* CBLTestCase.m */, + 40CC4BBE302D392D0002386D /* CBLTestCommon.h */, 1AC16CE6287D4D820041728F /* CollectionTest.m */, 9385F3021FC645AE00032037 /* ConcurrentTest.m */, 9378C5901E25B3F0001BB196 /* DatabaseTest.m */, @@ -4423,6 +4581,7 @@ 40600A3F2DD6B6A500E696B7 /* MultipeerReplicatorTest.h */, 40600A402DD6B6A500E696B7 /* MultipeerReplicatorTest.m */, 72A87A0E1E32E858008466FF /* NotificationTest.m */, + AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */, 27EF6A931E298E26004748DF /* PredicateQueryTest.m */, 93EB25CB21CDD12A0006FB88 /* PredictiveQueryTest.m */, 933F840C220BA4080093EC88 /* PredictiveQueryTest+CoreML.m */, @@ -4435,22 +4594,22 @@ 1A4160D6228367520061A567 /* ReplicatorTest.h */, 27E35A811E8B3B3A00E103F9 /* ReplicatorTest.m */, 1A4160D922836C7F0061A567 /* ReplicatorTest+Main.m */, + 40CC4BBF302D40A00002386D /* ReplicatorTest+Backgrounding.m */, 1A908400288027EE006B1885 /* ReplicatorTest+Collection.m */, - 1A0BFA4427B5273B00BA84E5 /* ReplicatorTest+SG.m */, 1A4160C52283673E0061A567 /* ReplicatorTest+CustomConflict.m */, 93F714202490971600624296 /* ReplicatorTest+MessageEndPoint.m */, 1ACDD8C223FF5BB200AF5D56 /* ReplicatorTest+PendingDocIds.m */, + 1A0BFA4427B5273B00BA84E5 /* ReplicatorTest+SG.m */, 934EF80B2453770E0053A47C /* TLSIdentityTest.m */, 1ACEB9662256B74A00DED54C /* TrustCheckTest.m */, 1A9617FF289BF6940037E78E /* URLEndpointListenerTest.h */, 1A6F0941246C78FC0097D8B5 /* URLEndpointListenerTest.m */, 1A13DD3F28B881BF00BC1084 /* URLEndpointListenerTest+Main.m */, 1A9617F2289BF3C10037E78E /* URLEndpointListenerTest+Collection.m */, + AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */, 40AA72A12C28B1F2007FB1E0 /* VectorSearchTest.h */, AE006DE62B7BB98B00884E2B /* VectorSearchTest.m */, 40AA72972C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m */, - AE5F25492CAC30DC00AAB7F4 /* UnnestArrayIndexTest.m */, - AEFEED742D4A8D6F008AF4C2 /* PartialIndexTest.m */, 93DECF3E200DBE5800F44953 /* Support */, 936483AA1E4431C6008D08B3 /* iOS */, 275FF5FA1E3FBD3B005F90DD /* Performance */, @@ -4965,7 +5124,6 @@ AE5CA6AD2E5E2584003A9E89 /* CBLMessageEndpointListenerConfiguration+Internal.h in Headers */, 40FC1BFF2B928AB100394276 /* CBLCert.h in Headers */, 40FC1C1F2B928B5000394276 /* CBLNoneVectorEncoding.h in Headers */, - 1AC7EC2A249DA24E00978C2E /* Foundation+CBL.h in Headers */, 9343EFC3207D611600F19A89 /* CBLMisc.h in Headers */, 93E18738211122EB001D52B9 /* MYURLUtils.h in Headers */, 9343EFC4207D611600F19A89 /* CBLBlob+Swift.h in Headers */, @@ -5372,7 +5530,6 @@ 937F01E61EFB280000060D64 /* CBLAuthenticator+Internal.h in Headers */, 9385F2661FC38F8900032037 /* CBLListenerToken.h in Headers */, AEA74F492CFE0BC3005F4810 /* CBLCustomLogSink.h in Headers */, - 1AC7EC29249DA24E00978C2E /* Foundation+CBL.h in Headers */, 93CD02721EA0004500AFB3FA /* CBLDocument.h in Headers */, 930B368E24AAFACB000DF2B3 /* CBLDocBranchIterator.h in Headers */, 934A27A51F30E62F003946A7 /* CBLUnaryExpression.h in Headers */, @@ -5577,6 +5734,67 @@ productReference = 275FF6131E3FE9DB005F90DD /* CBLPerfTests */; productType = "com.apple.product-type.tool"; }; + 40CC4B7F302C2FF90002386D /* CBL_EE_ObjC_Binary_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 40CC4B99302C2FF90002386D /* Build configuration list for PBXNativeTarget "CBL_EE_ObjC_Binary_Tests" */; + buildPhases = ( + 40CC4C17302D88950002386D /* Check Binary Frameworks */, + 40CC4B7C302C2FF90002386D /* Sources */, + 40CC4B7D302C2FF90002386D /* Frameworks */, + 40CC4B7E302C2FF90002386D /* Resources */, + 40CC4C14302D87810002386D /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CBL_EE_ObjC_Binary_Tests; + packageProductDependencies = ( + ); + productName = CBL_EE_ObjC_Binary_Tests; + productReference = 40CC4B80302C2FF90002386D /* CouchbaseLiteBinaryTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 40CC4C4D3033CE540002386D /* CBL_ObjC_Binary_Tests_iOS_App */ = { + isa = PBXNativeTarget; + buildConfigurationList = 40CC4C653033CE550002386D /* Build configuration list for PBXNativeTarget "CBL_ObjC_Binary_Tests_iOS_App" */; + buildPhases = ( + 40CC4C4A3033CE540002386D /* Sources */, + 40CC4C4B3033CE540002386D /* Frameworks */, + 40CC4C4C3033CE540002386D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CBL_ObjC_Binary_Tests_iOS_App; + packageProductDependencies = ( + ); + productName = CBL_ObjC_Binary_Tests_iOS_App; + productReference = 40CC4C4E3033CE540002386D /* CBL_Binary_Tests.app */; + productType = "com.apple.product-type.application"; + }; + 40CC4CFD303403F30002386D /* CBL_ObjC_Binary_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 40CC4D33303403F30002386D /* Build configuration list for PBXNativeTarget "CBL_ObjC_Binary_Tests" */; + buildPhases = ( + 40CC4CFE303403F30002386D /* Check Binary Frameworks */, + 40CC4CFF303403F30002386D /* Sources */, + 40CC4D2B303403F30002386D /* Frameworks */, + 40CC4D2E303403F30002386D /* Resources */, + 40CC4D30303403F30002386D /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CBL_ObjC_Binary_Tests; + packageProductDependencies = ( + ); + productName = CBL_EE_ObjC_Binary_Tests; + productReference = 40CC4D38303403F30002386D /* CouchbaseLiteBinaryTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 930B63CF246B9CD1006D94FF /* CBL_EE_Swift_Tests_iOS_App */ = { isa = PBXNativeTarget; buildConfigurationList = 930B63FD246B9CD2006D94FF /* Build configuration list for PBXNativeTarget "CBL_EE_Swift_Tests_iOS_App" */; @@ -5898,6 +6116,16 @@ 27BF031C1FB6290C003D5BB8 = { CreatedOnToolsVersion = 9.1; }; + 40CC4B7F302C2FF90002386D = { + CreatedOnToolsVersion = 26.4; + TestTargetID = 40CC4C4D3033CE540002386D; + }; + 40CC4C4D3033CE540002386D = { + CreatedOnToolsVersion = 26.4; + }; + 40CC4CFD303403F30002386D = { + TestTargetID = 40CC4C4D3033CE540002386D; + }; 930B63CF246B9CD1006D94FF = { CreatedOnToolsVersion = 10.1; DevelopmentTeam = N2Q372V7W2; @@ -5998,6 +6226,9 @@ 9343F135207D61EC00F19A89 /* CBL_EE_ObjC_Tests */, 9343F16F207D633300F19A89 /* CBL_EE_Objc_Tests_iOS */, 9343F153207D62C900F19A89 /* CBL_EE_Objc_Tests_iOS_App */, + 40CC4CFD303403F30002386D /* CBL_ObjC_Binary_Tests */, + 40CC4B7F302C2FF90002386D /* CBL_EE_ObjC_Binary_Tests */, + 40CC4C4D3033CE540002386D /* CBL_ObjC_Binary_Tests_iOS_App */, 9343F18A207D636300F19A89 /* CBL_EE_Swift_Tests */, 930B63E2246B9CD2006D94FF /* CBL_EE_Swift_Tests_iOS */, 930B63CF246B9CD1006D94FF /* CBL_EE_Swift_Tests_iOS_App */, @@ -6147,6 +6378,32 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 40CC4B7E302C2FF90002386D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4BA8302C49900002386D /* Support in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4C4C3033CE540002386D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4C6C3033CF3F0002386D /* Main.storyboard in Resources */, + 40CC4C6D3033CF3F0002386D /* LaunchScreen.storyboard in Resources */, + 40CC4C6B3033CF300002386D /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4D2E303403F30002386D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4D2F303403F30002386D /* Support in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 40EF690B2B7757A200F0CB50 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -6283,6 +6540,42 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ + 40CC4C17302D88950002386D /* Check Binary Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Check Binary Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Verify that binary frameworks are staged, and log what is being tested:\nINFO_FILE=\"${SRCROOT}/BinaryTests/Frameworks/info.txt\"\nif [ -f \"${INFO_FILE}\" ]; then\n cat \"${INFO_FILE}\"\nelse\n echo \"error: No binary frameworks staged in BinaryTests/Frameworks. Run 'Scripts/prepare_binary_test.sh [-]' first.\"\n exit 1\nfi\n"; + }; + 40CC4CFE303403F30002386D /* Check Binary Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Check Binary Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Verify that binary frameworks are staged, and log what is being tested:\nINFO_FILE=\"${SRCROOT}/BinaryTests/Frameworks/info.txt\"\nif [ -f \"${INFO_FILE}\" ]; then\n cat \"${INFO_FILE}\"\nelse\n echo \"error: No binary frameworks staged in BinaryTests/Frameworks. Run 'Scripts/prepare_binary_test.sh [-]' first.\"\n exit 1\nfi\n"; + }; 40EF68002B71889F00F0CB50 /* Remove Private Module */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -6607,7 +6900,6 @@ 934A279C1F30E5FA003946A7 /* CBLCompoundExpression.m in Sources */, 937A69411F10754D0058277F /* Meta.swift in Sources */, 934A27A21F30E61B003946A7 /* CBLPropertyExpression.m in Sources */, - 1AC7EC3A249DA70E00978C2E /* Foundation+CBL.mm in Sources */, 9308F4031E64B21F00F53EE4 /* CollectionUtils.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -6655,6 +6947,100 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 40CC4B7C302C2FF90002386D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4C493033BF560002386D /* QueryTest+Main.m in Sources */, + 40CC4BA4302C48240002386D /* CBLTestCase.m in Sources */, + 40CC4BCE302D4D7C0002386D /* URLEndpointListenerTest+Collection.m in Sources */, + 40CC4BCF302D4D7C0002386D /* URLEndpointListenerTest.m in Sources */, + 40CC4BD0302D4D7C0002386D /* URLEndpointListenerTest+Main.m in Sources */, + 40CC4BAA302CF2630002386D /* QueryTest+Meta.m in Sources */, + 40CC4C373033BF4D0002386D /* PredictiveQueryTest+CoreML.m in Sources */, + 40CC4BAB302CF2630002386D /* CollectionTest.m in Sources */, + 40CC4BB8302CF77C0002386D /* DatabaseTest.m in Sources */, + 40CC4BC4302D41A50002386D /* ReplicatorTest+Collection.m in Sources */, + 40CC4BC5302D41A50002386D /* ReplicatorTest+PendingDocIds.m in Sources */, + 40CC4BC6302D41A50002386D /* ReplicatorTest+MessageEndPoint.m in Sources */, + 40CC4BC7302D41A50002386D /* ReplicatorTest+Main.m in Sources */, + 40CC4BC8302D41A50002386D /* ReplicatorTest+CustomConflict.m in Sources */, + 40CC4BDE302D86290002386D /* VectorSearchTest.m in Sources */, + 40CC4BDF302D86290002386D /* VectorSearchTest+Lazy.m in Sources */, + 40CC4BC9302D41A50002386D /* ReplicatorTest.m in Sources */, + 40CC4BDC302D7AC00002386D /* UnnestArrayIndexTest.m in Sources */, + 40CC4BAC302CF2630002386D /* QueryTest.m in Sources */, + 40CC4BAD302CF2630002386D /* QueryTest+Join.m in Sources */, + 40CC4BDB302D77CA0002386D /* DocumentExpirationTest.m in Sources */, + 40CC4BAE302CF2630002386D /* QueryTest+Collection.m in Sources */, + 40CC4BAF302CF2630002386D /* NotificationTest.m in Sources */, + 40CC4BB0302CF2630002386D /* DateTimeQueryFunctionTest.m in Sources */, + 40CC4BB1302CF2630002386D /* PredictiveQueryTest.m in Sources */, + 40CC4BB4302CF2CD0002386D /* PartialIndexTest.m in Sources */, + 40CC4BA5302C48480002386D /* ArrayTest.m in Sources */, + 40CC4BA9302C4BE10002386D /* DocumentTest.m in Sources */, + 40CC4BB5302CF6250002386D /* CollectionUtils.m in Sources */, + 40CC4BCA302D41C80002386D /* CBLBlockConflictResolver.m in Sources */, + 40CC4BD1302D4DC50002386D /* AuthenticatorTest.m in Sources */, + 40CC4BDD302D860D0002386D /* Test_Assertions.m in Sources */, + 40CC4BD2302D4DC50002386D /* TLSIdentityTest.m in Sources */, + 40CC4BCB302D41C80002386D /* CBLTestCustomLogSink.m in Sources */, + 40CC4BCC302D41C80002386D /* CBLMockConnection.m in Sources */, + 40CC4BCD302D41C80002386D /* CBLMockConnectionErrorLogic.m in Sources */, + 40CC4BA6302C48530002386D /* DictionaryTest.m in Sources */, + 40CC4BA7302C48530002386D /* FragmentTest.m in Sources */, + 40CC4BE0302D86400002386D /* CBLWordEmbeddingModel.m in Sources */, + 40CC4BB6302CF68F0002386D /* ConcurrentTest.m in Sources */, + 40CC4BB7302CF68F0002386D /* DatabaseEncryptionTest.m in Sources */, + 40CC4BB3302CF2900002386D /* MigrationTest.m in Sources */, + 40CC4BA3302C44CB0002386D /* CBLJSONUtil.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4C4A3033CE540002386D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4C6E3033CF510002386D /* ViewController.m in Sources */, + 40CC4C6F3033CF510002386D /* main.m in Sources */, + 40CC4C6A3033CF280002386D /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 40CC4CFF303403F30002386D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 40CC4D00303403F30002386D /* QueryTest+Main.m in Sources */, + 40CC4D01303403F30002386D /* CBLTestCase.m in Sources */, + 40CC4D05303403F30002386D /* QueryTest+Meta.m in Sources */, + 40CC4D07303403F30002386D /* CollectionTest.m in Sources */, + 40CC4D08303403F30002386D /* DatabaseTest.m in Sources */, + 40CC4D0A303403F30002386D /* ReplicatorTest+PendingDocIds.m in Sources */, + 40CC4D10303403F30002386D /* ReplicatorTest.m in Sources */, + 40CC4D11303403F30002386D /* UnnestArrayIndexTest.m in Sources */, + 40CC4D12303403F30002386D /* QueryTest.m in Sources */, + 40CC4D13303403F30002386D /* QueryTest+Join.m in Sources */, + 40CC4D14303403F30002386D /* DocumentExpirationTest.m in Sources */, + 40CC4D15303403F30002386D /* QueryTest+Collection.m in Sources */, + 40CC4D16303403F30002386D /* NotificationTest.m in Sources */, + 40CC4D17303403F30002386D /* DateTimeQueryFunctionTest.m in Sources */, + 40CC4D19303403F30002386D /* PartialIndexTest.m in Sources */, + 40CC4D1A303403F30002386D /* ArrayTest.m in Sources */, + 40CC4D1B303403F30002386D /* DocumentTest.m in Sources */, + 40CC4D1C303403F30002386D /* CollectionUtils.m in Sources */, + 40CC4D1D303403F30002386D /* CBLBlockConflictResolver.m in Sources */, + 40CC4D1E303403F30002386D /* AuthenticatorTest.m in Sources */, + 40CC4D1F303403F30002386D /* Test_Assertions.m in Sources */, + 40CC4D21303403F30002386D /* CBLTestCustomLogSink.m in Sources */, + 40CC4D24303403F30002386D /* DictionaryTest.m in Sources */, + 40CC4D25303403F30002386D /* FragmentTest.m in Sources */, + 40CC4D27303403F30002386D /* ConcurrentTest.m in Sources */, + 40CC4D29303403F30002386D /* MigrationTest.m in Sources */, + 40CC4D2A303403F30002386D /* CBLJSONUtil.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 930B63CC246B9CD1006D94FF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -6709,7 +7095,6 @@ buildActionMask = 2147483647; files = ( EADC3CFF2D92E79000875416 /* CBLEncoder.mm in Sources */, - 1AC7EC2D249DA24E00978C2E /* Foundation+CBL.mm in Sources */, 9343EF2F207D611600F19A89 /* CBLDatabase.mm in Sources */, 1A1612B5283E29E600AA4987 /* CBLCollectionConfiguration.m in Sources */, 40FC1BDF2B928A4F00394276 /* CBLIndexBuilder+Prediction.m in Sources */, @@ -6970,7 +7355,6 @@ 40FC1C5E2B928C1600394276 /* MessageEndpointConnection.swift in Sources */, 40FC1B612B9287BD00394276 /* CBLURLEndpointListenerConfiguration.mm in Sources */, 9343F045207D61AB00F19A89 /* CBLQueryChange.m in Sources */, - 1AC7EC3B249DA70F00978C2E /* Foundation+CBL.mm in Sources */, 9343F048207D61AB00F19A89 /* CBLDatabaseConfiguration.m in Sources */, 9343F049207D61AB00F19A89 /* CBLFragment.m in Sources */, 9343F04A207D61AB00F19A89 /* CBLQueryArrayExpression.m in Sources */, @@ -7170,6 +7554,7 @@ 1A621D7A2887DCFD0017F905 /* QueryTest+Collection.m in Sources */, 93EB25CC21CDD12A0006FB88 /* PredictiveQueryTest.m in Sources */, AE5CA6512E590BFC003A9E89 /* MiscCppTest.mm in Sources */, + 40CC4B9F302C449D0002386D /* CBLJSONUtil.m in Sources */, 1AF555D422948BD90077DF6D /* QueryTest+Main.m in Sources */, 40AA729D2C28B1A3007FB1E0 /* VectorSearchTest+Lazy.m in Sources */, 9343F13D207D61EC00F19A89 /* MigrationTest.m in Sources */, @@ -7186,6 +7571,7 @@ 1A0BFA4827B5274D00BA84E5 /* ReplicatorTest+SG.m in Sources */, 9388CC4021C1E2B9005CA66D /* LogTest.m in Sources */, 9343F144207D61EC00F19A89 /* ArrayTest.m in Sources */, + 40CC4BC3302D40A00002386D /* ReplicatorTest+Backgrounding.m in Sources */, 1ACDD8D423FF5C4400AF5D56 /* ReplicatorTest+PendingDocIds.m in Sources */, 1A13DD4128B882A700BC1084 /* URLEndpointListenerTest+Main.m in Sources */, 9343F145207D61EC00F19A89 /* FragmentTest.m in Sources */, @@ -7218,6 +7604,10 @@ files = ( 40AA72952C28938D007FB1E0 /* VectorSearchTest.m in Sources */, 9343F173207D633300F19A89 /* ReplicatorTest.m in Sources */, + 40CC4BD3302D632C0002386D /* AuthenticatorTest.m in Sources */, + 40CC4BD4302D632C0002386D /* DateTimeQueryFunctionTest.m in Sources */, + 40CC4BD5302D632C0002386D /* MigrationTest.m in Sources */, + 40CC4BD6302D632C0002386D /* MiscCppTest.mm in Sources */, 1AF555D622948BE00077DF6D /* QueryTest+Main.m in Sources */, 1ABA63B128813A8D005835E7 /* ReplicatorTest+Collection.m in Sources */, 93F714222490971600624296 /* ReplicatorTest+MessageEndPoint.m in Sources */, @@ -7239,6 +7629,7 @@ 9343F179207D633300F19A89 /* QueryTest.m in Sources */, 1A621D7B2887DCFE0017F905 /* QueryTest+Collection.m in Sources */, AE5F255B2CAC311100AAB7F4 /* UnnestArrayIndexTest.m in Sources */, + 40CC4BC2302D40A00002386D /* ReplicatorTest+Backgrounding.m in Sources */, 933F841E220BA4100093EC88 /* PredictiveQueryTest+CoreML.m in Sources */, 1A2F2C3627FD5B2200084B3C /* TrustCheckTest.m in Sources */, 1A961801289BF7F90037E78E /* URLEndpointListenerTest+Collection.m in Sources */, @@ -7248,6 +7639,7 @@ 40938A522D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 40600A442DD6B6A500E696B7 /* MultipeerReplicatorTest.m in Sources */, 1A6F0951246C792A0097D8B5 /* URLEndpointListenerTest.m in Sources */, + 40CC4BA0302C449D0002386D /* CBLJSONUtil.m in Sources */, 9388CC4121C1E2BA005CA66D /* LogTest.m in Sources */, 9343F17C207D633300F19A89 /* FragmentTest.m in Sources */, 1AC16CF5287D4D9C0041728F /* CollectionTest.m in Sources */, @@ -7337,6 +7729,11 @@ 1AF555D522948BDF0077DF6D /* QueryTest+Main.m in Sources */, 1AA6743A227924110018CC6D /* QueryTest+Join.m in Sources */, 93CD018C1E95546200AFB3FA /* NotificationTest.m in Sources */, + 40CC4BD7302D634D0002386D /* DateTimeQueryFunctionTest.m in Sources */, + 40CC4BD8302D634D0002386D /* MigrationTest.m in Sources */, + 40CC4BD9302D634D0002386D /* AuthenticatorTest.m in Sources */, + 40CC4BDA302D634D0002386D /* MiscCppTest.mm in Sources */, + 40CC4BA2302C44CB0002386D /* CBLJSONUtil.m in Sources */, 9388CC3F21C1E2B8005CA66D /* LogTest.m in Sources */, 40938A542D4B464500691393 /* CBLTestCustomLogSink.m in Sources */, 938CFA2F1E442B5700291631 /* DatabaseTest.m in Sources */, @@ -7415,7 +7812,6 @@ AEA74F322CFE0581005F4810 /* CBLFileLogSink.mm in Sources */, 72A87A061E2E0E70008466FF /* CBLBlobStream.mm in Sources */, 93E18735211122D9001D52B9 /* MYURLUtils.m in Sources */, - 1AC7EC2B249DA24E00978C2E /* Foundation+CBL.mm in Sources */, 93EB25C321CDCEC20006FB88 /* CBLQueryParameters.mm in Sources */, 937A69051F0731230058277F /* CBLQueryFunction.m in Sources */, 93DBD0132004BCE00017CA83 /* CBLURLEndpoint.m in Sources */, @@ -7492,6 +7888,7 @@ 1AF555C422946ED90077DF6D /* QueryTest+Main.m in Sources */, 9378C5971E25B473001BB196 /* CBLTestCase.m in Sources */, 9378C5911E25B3F0001BB196 /* DatabaseTest.m in Sources */, + 40CC4BA1302C44CB0002386D /* CBLJSONUtil.m in Sources */, 1A4FE76A225ED344009D5F43 /* MiscCppTest.mm in Sources */, AE5F25582CAC310700AAB7F4 /* UnnestArrayIndexTest.m in Sources */, 1AC83BC821C026D100792098 /* DateTimeQueryFunctionTest.m in Sources */, @@ -8390,6 +8787,340 @@ }; name = Release; }; + 40CC4B84302C2FF90002386D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 40CC4B85302C2FF90002386D /* Debug_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Debug_EE; + }; + 40CC4B86302C2FF90002386D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 40CC4B87302C2FF90002386D /* Release_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9A302C31BD0002386D /* CBL_EE_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Release_EE; + }; + 40CC4C663033CE550002386D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Debug; + }; + 40CC4C673033CE550002386D /* Debug_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + }; + name = Debug_EE; + }; + 40CC4C683033CE550002386D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 40CC4C693033CE550002386D /* Release_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4CAD3033EEF90002386D /* CBL_ObjC_Binary_Tests_iOS_App.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + VALIDATE_PRODUCT = YES; + }; + name = Release_EE; + }; + 40CC4D34303403F30002386D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Debug; + }; + 40CC4D35303403F30002386D /* Debug_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Debug_EE; + }; + 40CC4D36303403F30002386D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Release; + }; + 40CC4D37303403F30002386D /* Release_EE */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 40CC4B9B302C31D00002386D /* CBL_ObjC_Binary_Tests.xcconfig */; + buildSettings = { + }; + name = Release_EE; + }; 930B63EA246B9CD2006D94FF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 93BB1C7C246BB151004FFA00 /* CBL_EE_Swift_Tests_iOS_App.xcconfig */; @@ -10312,6 +11043,39 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 40CC4B99302C2FF90002386D /* Build configuration list for PBXNativeTarget "CBL_EE_ObjC_Binary_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 40CC4B84302C2FF90002386D /* Debug */, + 40CC4B85302C2FF90002386D /* Debug_EE */, + 40CC4B86302C2FF90002386D /* Release */, + 40CC4B87302C2FF90002386D /* Release_EE */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 40CC4C653033CE550002386D /* Build configuration list for PBXNativeTarget "CBL_ObjC_Binary_Tests_iOS_App" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 40CC4C663033CE550002386D /* Debug */, + 40CC4C673033CE550002386D /* Debug_EE */, + 40CC4C683033CE550002386D /* Release */, + 40CC4C693033CE550002386D /* Release_EE */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 40CC4D33303403F30002386D /* Build configuration list for PBXNativeTarget "CBL_ObjC_Binary_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 40CC4D34303403F30002386D /* Debug */, + 40CC4D35303403F30002386D /* Debug_EE */, + 40CC4D36303403F30002386D /* Release */, + 40CC4D37303403F30002386D /* Release_EE */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 930B63FD246B9CD2006D94FF /* Build configuration list for PBXNativeTarget "CBL_EE_Swift_Tests_iOS_App" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests.xcscheme b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests.xcscheme new file mode 100644 index 000000000..832c7161e --- /dev/null +++ b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests_iOS_App.xcscheme b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests_iOS_App.xcscheme new file mode 100644 index 000000000..ebe0f3353 --- /dev/null +++ b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests_iOS_App.xcscheme @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests.xcscheme b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests.xcscheme new file mode 100644 index 000000000..03be5cf91 --- /dev/null +++ b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests.xcscheme @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests_iOS_App.xcscheme b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests_iOS_App.xcscheme new file mode 100644 index 000000000..c61dfeaab --- /dev/null +++ b/CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests_iOS_App.xcscheme @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Objective-C/CBLCollectionConfiguration.m b/Objective-C/CBLCollectionConfiguration.m index 232555f68..bba647fb3 100644 --- a/Objective-C/CBLCollectionConfiguration.m +++ b/Objective-C/CBLCollectionConfiguration.m @@ -57,20 +57,6 @@ - (instancetype) initWithConfig: (CBLCollectionConfiguration*)config { return configs; } -+ (NSArray*) fromCollections:(NSArray*)collections - config:(void (^)(CBLCollectionConfiguration* config))config { - [CBLPrecondition assertArrayNotEmpty:collections name:@"collections"]; - NSMutableArray* configs = [NSMutableArray arrayWithCapacity:collections.count]; - for (CBLCollection* collection in collections) { - CBLCollectionConfiguration* colConfig = [[CBLCollectionConfiguration alloc] initWithCollection:collection]; - if (config) { - config(colConfig); - } - [configs addObject:colConfig]; - } - return configs; -} - - (NSDictionary*) effectiveOptions { NSMutableDictionary* options = [NSMutableDictionary dictionary]; options[@kC4ReplicatorOptionChannels] = self.channels; diff --git a/Objective-C/CBLDatabase.mm b/Objective-C/CBLDatabase.mm index 8f4e1ea6a..6891ee062 100644 --- a/Objective-C/CBLDatabase.mm +++ b/Objective-C/CBLDatabase.mm @@ -39,7 +39,6 @@ #import "CBLStatus.h" #import "CBLStringBytes.h" #import "CBLVersion.h" -#import "Foundation+CBL.h" #import "c4BlobStore.h" #import "c4Observer.h" #import "fleece/Fleece.hh" @@ -704,12 +703,6 @@ - (BOOL) isClosed { } } -- (BOOL) isClosedLocked { - CBL_LOCK(_mutex) { - return [self isClosed]; - } -} - - (C4SliceResult) getPublicUUID: (NSError**)outError { CBL_LOCK(_mutex) { if (![self mustBeOpen: outError]) @@ -914,12 +907,6 @@ - (void) unregisterActiveService: (id)service { } } -- (uint64_t) activeServiceCount { - CBL_LOCK(_mutex) { - return _activeServices.count; - } -} - #pragma mark - Private for test - (const C4DatabaseConfig2*) getC4DBConfig { diff --git a/Objective-C/Internal/CBLDatabase+Internal.h b/Objective-C/Internal/CBLDatabase+Internal.h index 21f49286a..9217c2059 100644 --- a/Objective-C/Internal/CBLDatabase+Internal.h +++ b/Objective-C/Internal/CBLDatabase+Internal.h @@ -55,7 +55,6 @@ NS_ASSUME_NONNULL_BEGIN - (BOOL) mustBeOpen: (NSError**)outError; - (void) mustBeOpenLocked; -- (BOOL) isClosedLocked; - (C4SliceResult) getPublicUUID: (NSError**)outError; @@ -63,7 +62,6 @@ NS_ASSUME_NONNULL_BEGIN - (void) registerActiveService: (id)service; - (void) unregisterActiveService: (id)service; -- (uint64_t) activeServiceCount; // For testing only // Initialize the CBLDatabase with a give C4Database object in the shell mode. // This is currently used for creating a CBLDictionary as an input of the predict() diff --git a/Objective-C/Internal/Replicator/CBLCollectionConfiguration+Internal.h b/Objective-C/Internal/Replicator/CBLCollectionConfiguration+Internal.h index 706a01668..3fe57dddc 100644 --- a/Objective-C/Internal/Replicator/CBLCollectionConfiguration+Internal.h +++ b/Objective-C/Internal/Replicator/CBLCollectionConfiguration+Internal.h @@ -28,20 +28,6 @@ NS_ASSUME_NONNULL_BEGIN - (NSDictionary*) effectiveOptions; -/** - Creates an array of `CBLCollectionConfiguration` objects from the given collections with the same configuration closure. - - This is a convenience method for configuring multiple collections with the same configurations. - If custom configurations are needed, construct `CBLCollectionConfiguration` objects - directly instead. - - @param collections The collections to replicate. - @param config A block to configure all `CBLCollectionConfiguration` object. - @return An array of CBLCollectionConfiguration objects for the given collections. - */ -+ (NSArray*) fromCollections: (NSArray*)collections - config: (void (^)(CBLCollectionConfiguration* config))config; - @end NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/ArrayTest.m b/Objective-C/Tests/ArrayTest.m index 5992bee9f..b3f72d47c 100644 --- a/Objective-C/Tests/ArrayTest.m +++ b/Objective-C/Tests/ArrayTest.m @@ -18,8 +18,7 @@ // #import "CBLTestCase.h" -#import "CBLJSON.h" -#import "Foundation+CBL.h" +#import "CBLJSONUtil.h" #define kArrayTestDate @"2017-01-01T00:00:00.000Z" #define kArrayTestBlob @"i'm blob" @@ -43,7 +42,7 @@ - (NSArray*) arrayOfAllTypes { [array addObject: @(1)]; [array addObject: @(-1)]; [array addObject: @(1.1)]; - [array addObject: [CBLJSON dateWithJSONObject: kArrayTestDate]]; + [array addObject: [CBLJSONUtil dateFromJSONDateString: kArrayTestDate]]; [array addObject: [NSNull null]]; CBLMutableDictionary* dict = [[CBLMutableDictionary alloc] init]; @@ -840,7 +839,7 @@ - (void) testGetDate { AssertNil([a dateAtIndex: 4]); AssertNil([a dateAtIndex: 5]); AssertNil([a dateAtIndex: 6]); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [a dateAtIndex: 7]], kArrayTestDate); + AssertEqualObjects([CBLJSONUtil jsonDateString: [a dateAtIndex: 7]], kArrayTestDate); AssertNil([a dateAtIndex: 8]); AssertNil([a dateAtIndex: 9]); AssertNil([a dateAtIndex: 10]); @@ -1085,7 +1084,7 @@ - (void) testArrayToJSON { CBLDocument* retrivedDoc = [self.defaultCollection documentWithID: @"doc" error: nil]; CBLArray* a = [retrivedDoc arrayForKey: @"array"]; AssertEqual(a.count, 2); - AssertEqualObjects([[a toJSON] toJSONObj], [json toJSONObj]); + AssertEqualObjects([CBLJSONUtil jsonObjectFromString: [a toJSON]], [CBLJSONUtil jsonObjectFromString: json]); } - (void) testGetBlobContentFromMutableObject { diff --git a/Objective-C/Tests/AuthenticatorTest.m b/Objective-C/Tests/AuthenticatorTest.m index 93f54b5e1..945882857 100644 --- a/Objective-C/Tests/AuthenticatorTest.m +++ b/Objective-C/Tests/AuthenticatorTest.m @@ -18,9 +18,10 @@ // #import -#import "CBLBasicAuthenticator.h" #import "CBLTestCase.h" +#ifndef CBL_BINARY_TEST #import "CBLAuthenticator+Internal.h" +#endif @interface AuthenticatorTest : CBLTestCase @@ -38,22 +39,6 @@ - (void) testBasicAuthenticatorInstance { AssertEqualObjects([auth password], password); } -- (void) testBasicAuthenticatorAuthenticate { - NSString* username = @"someUsername"; - NSString* password = @"somePassword"; - - CBLBasicAuthenticator* auth = [[CBLBasicAuthenticator alloc] initWithUsername: username - password: password]; - - NSMutableDictionary* options = [NSMutableDictionary dictionary]; - AssertEqualObjects(options, @{}); - - [auth authenticate: options]; - AssertEqualObjects(options[@"auth"][@"type"], @"Basic"); - AssertEqualObjects(options[@"auth"][@"username"], username); - AssertEqualObjects(options[@"auth"][@"password"], password); -} - - (void) testSessionAuthenticatorWithSessionID { NSString* sessionID = @"someSessionID"; @@ -81,6 +66,27 @@ - (void) testSessionAuthenticatorEmptyCookie { AssertEqualObjects([auth cookieName], @"SyncGatewaySession"); } +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testBasicAuthenticatorAuthenticate { + NSString* username = @"someUsername"; + NSString* password = @"somePassword"; + + CBLBasicAuthenticator* auth = [[CBLBasicAuthenticator alloc] initWithUsername: username + password: password]; + + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + AssertEqualObjects(options, @{}); + + [auth authenticate: options]; + AssertEqualObjects(options[@"auth"][@"type"], @"Basic"); + AssertEqualObjects(options[@"auth"][@"username"], username); + AssertEqualObjects(options[@"auth"][@"password"], password); +} + - (void) testAuthenticateSession { NSString* sessionID = @"someSessionID"; NSString* cookie = @"someCookie"; @@ -97,5 +103,6 @@ - (void) testAuthenticateSession { AssertEqualObjects(options[@"cookies"], cookies); } -@end +#endif +@end diff --git a/Objective-C/Tests/CBLTestCase.h b/Objective-C/Tests/CBLTestCase.h index 82712bdac..01f1dd635 100644 --- a/Objective-C/Tests/CBLTestCase.h +++ b/Objective-C/Tests/CBLTestCase.h @@ -18,13 +18,16 @@ // #import -#import "CouchbaseLite.h" +#import "CBLTestCommon.h" + +#ifndef CBL_BINARY_TEST #ifdef __cplusplus #import #else #import #endif +#endif #define Assert XCTAssert #define AssertNil XCTAssertNil @@ -64,11 +67,13 @@ #endif +#ifndef CBL_BINARY_TEST #ifdef __cplusplus extern std::atomic_int gC4ExpectExceptions; #else extern atomic_int gC4ExpectExceptions; #endif +#endif #define kDatabaseName @"testdb" #define kOtherDatabaseName @"otherdb" diff --git a/Objective-C/Tests/CBLTestCase.m b/Objective-C/Tests/CBLTestCase.m index 54feed911..3a53453bf 100644 --- a/Objective-C/Tests/CBLTestCase.m +++ b/Objective-C/Tests/CBLTestCase.m @@ -18,8 +18,17 @@ // #import "CBLTestCase.h" + +#ifndef CBL_BINARY_TEST #include "c4.h" -#import "CollectionUtils.h" +// Marks a scope that intentionally provokes exceptions, so that LiteCore's +// exception diagnostics don't flag them. No-op when testing a binary framework. +#define CBLExpectExceptionsBegin() ((void)++gC4ExpectExceptions) +#define CBLExpectExceptionsEnd() ((void)--gC4ExpectExceptions) +#else +#define CBLExpectExceptionsBegin() +#define CBLExpectExceptionsEnd() +#endif #ifdef COUCHBASE_ENTERPRISE #define kDatabaseDirName @"CouchbaseLite_EE" @@ -48,7 +57,9 @@ - (void) setUp { [self deleteDBNamed: kDatabaseName error: nil]; [self deleteDBNamed: kOtherDatabaseName error: nil]; +#ifndef CBL_BINARY_TEST _c4ObjectCount = c4_getObjectCount(); +#endif NSString* dir = self.directory; if ([[NSFileManager defaultManager] fileExistsAtPath: dir]) { NSError* error; @@ -69,6 +80,7 @@ - (void) tearDown { _otherDB = nil; } +#ifndef CBL_BINARY_TEST if (!_disableObjectLeakCheck) { // Wait a little while for objects to be cleaned up: __block int leaks = 0; @@ -82,7 +94,8 @@ - (void) tearDown { XCTFail("%d LiteCore objects have not been freed (see above)", leaks); } } - + +#endif [super tearDown]; } @@ -306,7 +319,8 @@ - (void) loadJSONString: (NSString*)contents __block uint64_t n = 0; [contents enumerateLinesUsingBlock: ^(NSString *line, BOOL *stop) { NSError* err; - CBLMutableDocument* doc = [[CBLMutableDocument alloc] initWithID: $sprintf(@"doc-%03llu", ++n) + NSString* docID = [NSString stringWithFormat: @"doc-%03llu", ++n]; + CBLMutableDocument* doc = [[CBLMutableDocument alloc] initWithID: docID json: line error: &err]; Assert([collection saveDocument: doc error: &err], @"Couldn't save document: %@", err); }]; @@ -363,10 +377,10 @@ - (void) expectError: (NSErrorDomain)domain code: (NSInteger)code in: (BOOL (^)( if ([self isProfiling]) return; - ++gC4ExpectExceptions; + CBLExpectExceptionsBegin(); NSError* error; BOOL succeeded = block(&error); - --gC4ExpectExceptions; + CBLExpectExceptionsEnd(); if (succeeded) { XCTFail("Block expected to fail but didn't"); @@ -381,9 +395,9 @@ - (void) expectException: (NSString*)name in: (void (^) (void))block { if ([self isProfiling]) return; - ++gC4ExpectExceptions; + CBLExpectExceptionsBegin(); XCTAssertThrowsSpecificNamed(block(), NSException, name); - --gC4ExpectExceptions; + CBLExpectExceptionsEnd(); } - (void) mayHaveException: (NSString*)name in: (void (^) (void))block { @@ -391,14 +405,14 @@ - (void) mayHaveException: (NSString*)name in: (void (^) (void))block { return; @try { - ++gC4ExpectExceptions; + CBLExpectExceptionsBegin(); block(); } @catch (NSException* e) { AssertEqualObjects(e.name, name); } @finally { - --gC4ExpectExceptions; + CBLExpectExceptionsEnd(); } } @@ -407,19 +421,19 @@ - (void) ignoreException: (void (^) (void))block { return; @try { - ++gC4ExpectExceptions; + CBLExpectExceptionsBegin(); block(); } @catch (NSException* e) { } @finally { - --gC4ExpectExceptions; + CBLExpectExceptionsEnd(); } } - (void) ignoreExceptionBreakPoint: (void (^) (void))block { - ++gC4ExpectExceptions; + CBLExpectExceptionsBegin(); block(); - --gC4ExpectExceptions; + CBLExpectExceptionsEnd(); } - (uint64_t) verifyQuery: (CBLQuery*)q diff --git a/Objective-C/Internal/Foundation+CBL.mm b/Objective-C/Tests/CBLTestCommon.h similarity index 55% rename from Objective-C/Internal/Foundation+CBL.mm rename to Objective-C/Tests/CBLTestCommon.h index b5dd80a93..a3f4a8515 100644 --- a/Objective-C/Internal/Foundation+CBL.mm +++ b/Objective-C/Tests/CBLTestCommon.h @@ -1,8 +1,8 @@ // -// Foundation+CBL.mm +// CBLTestCommon.h // CouchbaseLite // -// Copyright (c) 2020 Couchbase, Inc All rights reserved. +// Copyright (c) 2026 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,17 +17,14 @@ // limitations under the License. // -#import "Foundation+CBL.h" +#pragma once -@implementation NSString (CBL) -- (id) toJSONObj { - NSData* d = [self dataUsingEncoding: NSUTF8StringEncoding]; - - NSError* error; - id retrivedObj = [NSJSONSerialization JSONObjectWithData: d options: 0 - error: &error]; - AssertNil(error); - return retrivedObj; -} - -@end +// The binary test targets define CBL_BINARY_TEST (see +// xcconfigs/CBL_ObjC_Binary_Tests.xcconfig): they compile against a staged +// binary framework, populated by Scripts/prepare_binary_test.sh, instead of +// the source tree, and so have no access to internal headers or diagnostics. +#ifdef CBL_BINARY_TEST +#import +#else +#import "CouchbaseLite.h" +#endif diff --git a/Objective-C/Tests/ConcurrentTest.m b/Objective-C/Tests/ConcurrentTest.m index 8eda323aa..284618a77 100644 --- a/Objective-C/Tests/ConcurrentTest.m +++ b/Objective-C/Tests/ConcurrentTest.m @@ -18,7 +18,6 @@ // #import "CBLTestCase.h" -#import "CollectionUtils.h" #define kDocumentTestBlob @"i'm blob" @@ -156,9 +155,9 @@ - (void) testConcurrentReadDocs { NSArray* docs = [self createAndSaveDocs: kNDocs error: nil]; - NSArray* docIds = [docs my_map: ^id(CBLDocument* doc) { - return doc.id; - }]; + NSMutableArray* docIds = [NSMutableArray arrayWithCapacity: docs.count]; + for (CBLDocument* doc in docs) + [docIds addObject: doc.id]; [self concurrentRuns: kNConcurrents waitUntilDone: YES withBlock: ^(NSUInteger rIndex) { for (NSUInteger r = 0; r < kNRounds; r++) { @@ -180,9 +179,9 @@ - (void) testConcurrentReadForUpdatesDocs { const NSUInteger kNConcurrents = 5; NSArray* docs = [self createAndSaveDocs: kNDocs error: nil]; - NSArray* docIds = [docs my_map: ^id(CBLDocument* doc) { - return doc.id; - }]; + NSMutableArray* docIds = [NSMutableArray arrayWithCapacity: docs.count]; + for (CBLDocument* doc in docs) + [docIds addObject: doc.id]; [self concurrentRuns: kNConcurrents waitUntilDone: YES withBlock: ^(NSUInteger rIndex) { for (NSUInteger r = 0; r < kNRounds; r++) { @@ -202,9 +201,9 @@ - (void) testConcurrentUpdateSeperateDocInstances { const NSUInteger kNConcurrents = 5; NSArray* docs = [self createAndSaveDocs: kNDocs error: nil]; - NSArray* docIds = [docs my_map: ^id(CBLDocument* doc) { - return doc.id; - }]; + NSMutableArray* docIds = [NSMutableArray arrayWithCapacity: docs.count]; + for (CBLDocument* doc in docs) + [docIds addObject: doc.id]; [self concurrentRuns: kNConcurrents waitUntilDone: YES withBlock: ^(NSUInteger rIndex) { NSString* tag = [NSString stringWithFormat:@"Update%ld", (long)rIndex]; diff --git a/Objective-C/Tests/DatabaseEncryptionTest.m b/Objective-C/Tests/DatabaseEncryptionTest.m index c35144812..085cfc6ee 100644 --- a/Objective-C/Tests/DatabaseEncryptionTest.m +++ b/Objective-C/Tests/DatabaseEncryptionTest.m @@ -18,7 +18,6 @@ // #import "CBLTestCase.h" -#import "CBLDatabase+Internal.h" @interface DatabaseEncryptionTest : CBLTestCase diff --git a/Objective-C/Tests/DatabaseTest.m b/Objective-C/Tests/DatabaseTest.m index ee1c4f323..65928278e 100644 --- a/Objective-C/Tests/DatabaseTest.m +++ b/Objective-C/Tests/DatabaseTest.m @@ -18,11 +18,10 @@ // #import "CBLTestCase.h" +#ifndef CBL_BINARY_TEST #import "CBLDatabase+Internal.h" -#import "CBLDocument+Internal.h" -#import "CBLScope.h" +#endif #import "CollectionUtils.h" -#import "CBLQueryFullTextIndexExpressionProtocol.h" @interface DatabaseTest : CBLTestCase @end @@ -254,12 +253,8 @@ - (void) testCloseWithActiveLiveQueries { [self waitForExpectations: @[change1, change2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - [self closeDatabase: self.db]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } #ifdef COUCHBASE_ENTERPRISE @@ -287,14 +282,10 @@ - (void) testCloseWithActiveReplicators { [self waitForExpectations: @[idle1, idle2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - [self closeDatabase: self.db]; [self waitForExpectations: @[stopped1, stopped2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } - (void) testCloseWithActiveLiveQueriesAndReplicators { @@ -311,8 +302,6 @@ - (void) testCloseWithActiveLiveQueriesAndReplicators { [self waitForExpectations: @[change1, change2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - // Replicators: [self openOtherDB]; @@ -337,16 +326,11 @@ - (void) testCloseWithActiveLiveQueriesAndReplicators { [self waitForExpectations: @[idle1, idle2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)4); // total services - // Close database: [self closeDatabase: self.db]; [self waitForExpectations: @[stopped1, stopped2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } - (void) startReplicator: (CBLReplicator*)repl @@ -419,7 +403,6 @@ - (void) testDeleteThenAccessBlob { AssertNotNil(blob1.content); AssertEqualObjects(blob.content, blob1.content); - // Content shouldn't be accessible from doc1: Assert([[doc1 valueForKey: @"data"] isKindOfClass: [CBLBlob class]]); CBLBlob* blob2= [doc1 valueForKey: @"data"]; @@ -477,12 +460,8 @@ - (void) testDeleteWithActiveLiveQueries { [self waitForExpectations: @[change1, change2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - [self deleteDatabase: self.db]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } #ifdef COUCHBASE_ENTERPRISE @@ -510,14 +489,10 @@ - (void) testDeleteWithActiveReplicators { [self waitForExpectations: @[idle1, idle2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - [self deleteDatabase: self.db]; [self waitForExpectations: @[stopped1, stopped2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } - (void) testDeleteWithActiveLiveQueriesAndReplicators { @@ -534,8 +509,6 @@ - (void) testDeleteWithActiveLiveQueriesAndReplicators { [self waitForExpectations: @[change1, change2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)2); - CBLDatabaseEndpoint* target = [[CBLDatabaseEndpoint alloc] initWithDatabase: self.otherDB]; CBLCollectionConfiguration* collectionConfig = [[CBLCollectionConfiguration alloc] initWithCollection: self.defaultCollection]; CBLReplicatorConfiguration* config = @@ -556,15 +529,10 @@ - (void) testDeleteWithActiveLiveQueriesAndReplicators { [self waitForExpectations: @[idle1, idle2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)4); // total services - [self deleteDatabase: self.db]; [self waitForExpectations: @[stopped1, stopped2] timeout: kExpTimeout]; - AssertEqual([self.db activeServiceCount], (unsigned long)0); - AssertEqual([self.db activeServiceCount], (unsigned long)0); - Assert([self.db isClosedLocked]); } #endif @@ -1099,7 +1067,6 @@ - (void) testFTSQueryWithJoin { [doc setString: @"en" forKey: @"lang"]; [self saveDocument: doc collection: colA]; - id qualifiedIndex = [[CBLQueryExpression fullTextIndex: @"passageIndex"] from: @"main"]; CBLQuerySelectResult* S_DOCID = [CBLQuerySelectResult expression: [CBLQueryMeta idFrom: @"main"]]; @@ -1226,7 +1193,6 @@ - (void) testCollectionIndex { CBLValueIndexConfiguration* config = [[CBLValueIndexConfiguration alloc] initWithExpression: @[@"firstName", @"lastName"]]; Assert([c createIndexWithName: @"index1" config: config error: &error]); - // index2 CBLFullTextIndexConfiguration* config2 = [[CBLFullTextIndexConfiguration alloc] initWithExpression: @[@"detail"] ignoreAccents: NO @@ -1307,34 +1273,36 @@ - (void) testSQLiteFullSyncConfig { 7. Get the configuration object from the Database and verify that FullSync is true. 8. Use c4db_config2 to confirm that its config contains the kC4DB_DiskSyncFull flag. */ -- (void) testDBWithFullSync { - NSString* dbName = @"fullsyncdb"; - [CBLDatabase deleteDatabase: dbName inDirectory: self.directory error: nil]; - AssertFalse([CBLDatabase databaseExists: dbName inDirectory: self.directory]); - + +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testFullSyncConfig { + // fullSync is off by default: CBLDatabaseConfiguration* config = [[CBLDatabaseConfiguration alloc] init]; config.directory = self.directory; NSError* error; - CBLDatabase* db = [[CBLDatabase alloc] initWithName: dbName + CBLDatabase* db = [[CBLDatabase alloc] initWithName: @"fullsyncdb" config: config error: &error]; - AssertNil(error); AssertNotNil(db, @"Couldn't open db: %@", error); AssertFalse([db config].fullSync); AssertFalse(([db getC4DBConfig]->flags & kC4DB_DiskSyncFull) == kC4DB_DiskSyncFull); - [self closeDatabase: db]; - + + // fullSync is enabled in the LiteCore database config: config.fullSync = true; - db = [[CBLDatabase alloc] initWithName: dbName + db = [[CBLDatabase alloc] initWithName: @"fullsyncdb" config: config error: &error]; - AssertNil(error); AssertNotNil(db, @"Couldn't open db: %@", error); Assert([db config].fullSync); Assert(([db getC4DBConfig]->flags & kC4DB_DiskSyncFull) == kC4DB_DiskSyncFull); - [self closeDatabase: db]; } +#endif + @end diff --git a/Objective-C/Tests/DictionaryTest.m b/Objective-C/Tests/DictionaryTest.m index a8074e203..f2ae5706e 100644 --- a/Objective-C/Tests/DictionaryTest.m +++ b/Objective-C/Tests/DictionaryTest.m @@ -18,8 +18,7 @@ // #import "CBLTestCase.h" -#import "CBLJSON.h" -#import "Foundation+CBL.h" +#import "CBLJSONUtil.h" @interface DictionaryTest : CBLTestCase @@ -364,8 +363,8 @@ - (void) testDictionaryToJSON { [self saveDocument: mDoc collection: self.defaultCollection]; CBLDocument* doc = [self.defaultCollection documentWithID: @"doc" error: &error]; CBLDictionary* dict = [doc dictionaryForKey: @"dict"]; - NSDictionary* jsonDict = [[dict toJSON] toJSONObj]; - AssertEqualObjects(jsonDict, [json toJSONObj]); + NSDictionary* jsonDict = [CBLJSONUtil jsonObjectFromString: [dict toJSON]]; + AssertEqualObjects(jsonDict, [CBLJSONUtil jsonObjectFromString: json]); AssertEqualObjects(jsonDict[@"name"], @"Rick Sanchez"); AssertEqualObjects(jsonDict[@"id"], @1); AssertEqualObjects(jsonDict[@"isAlive"], @YES); @@ -386,9 +385,9 @@ - (void) testDictionaryToJSON { doc = [self.defaultCollection documentWithID: @"doc" error: &error]; dict = [doc dictionaryForKey: @"dict"]; - NSMutableDictionary* appendedDict = [NSMutableDictionary dictionaryWithDictionary: [json toJSONObj]]; + NSMutableDictionary* appendedDict = [NSMutableDictionary dictionaryWithDictionary: [CBLJSONUtil jsonObjectFromString: json]]; appendedDict[@"newKeyAppended"] = @"newValueAppended"; - AssertEqualObjects([[dict toJSON] toJSONObj], appendedDict); + AssertEqualObjects([CBLJSONUtil jsonObjectFromString: [dict toJSON]], appendedDict); } - (void) testUnsavedMutableDictionaryToJSON { diff --git a/Objective-C/Tests/DocumentExpirationTest.m b/Objective-C/Tests/DocumentExpirationTest.m index 43caa4918..f3cd60f52 100644 --- a/Objective-C/Tests/DocumentExpirationTest.m +++ b/Objective-C/Tests/DocumentExpirationTest.m @@ -18,7 +18,7 @@ // #import "CBLTestCase.h" -#import "CBLDocument+Internal.h" + #define kDOCID [CBLQuerySelectResult expression: [CBLQueryMeta id]] @@ -447,59 +447,62 @@ - (void) testRemoveExpirationDate { [self waitForExpectationsWithTimeout: kExpTimeout handler: nil]; } -- (void) testSetExpirationThenDeletionAfterwards { + +- (void) testPurgeImmediately { NSError* error; XCTestExpectation* expectation = [self expectationWithDescription: @"Document expiry test"]; // Create doc CBLDocument* doc = [self generateDocumentWithID: nil]; + AssertNil([self.defaultCollection getDocumentExpirationWithID: doc.id error: &error]); // Setup document change notification - __block int count = 0; + __block NSDate* purgeTime; id token = [self.defaultCollection addDocumentChangeListenerWithID: doc.id listener: ^(CBLDocumentChange *change) { NSError* err; - count++; AssertEqualObjects(change.documentID, doc.id); - AssertNil([change.collection documentWithID: change.documentID error: &err]); - if (count == 2) { - CBLDocument* purgedDoc = [[CBLDocument alloc] initWithCollection: self.defaultCollection - documentID: doc.id - includeDeleted: YES - error: nil]; - AssertNil(purgedDoc); + if ([change.collection documentWithID: change.documentID error: &err] == nil) { + purgeTime = [NSDate date]; [expectation fulfill]; } }]; // Set expiry - NSDate* expiryDate = [NSDate dateWithTimeIntervalSinceNow: 2.0]; + NSDate* begin = [NSDate date]; Assert([self.defaultCollection setDocumentExpirationWithID: doc.id - expiration: expiryDate + expiration: begin error: &error]); AssertNil(error); - // Delete doc - Assert([self.defaultCollection deleteDocument: doc error: &error]); - AssertNil(error); - AssertNil([self.defaultCollection documentWithID: doc.id error: &error]); - - CBLDocument* deletedDoc = [[CBLDocument alloc] initWithCollection: self.defaultCollection - documentID: doc.id - includeDeleted: TRUE - error: &error]; - AssertNotNil(deletedDoc); - // Wait for result [self waitForExpectationsWithTimeout: kExpTimeout handler: nil]; - AssertEqual(count, 2); + + /* + Validate. Delay inside the KeyStore::now() is in seconds, without milliseconds part. + Depending on the current milliseconds, we cannot gurantee, this will get purged exactly within + a second but in ~1 second. + */ + NSTimeInterval delta = [purgeTime timeIntervalSinceDate: begin]; + Assert(delta < 2); // Remove listener [token remove]; } -- (void) testSetExpirationOnDeletedDocument { +- (NSUInteger) deletedDocumentCount: (NSString*)docID { + NSError* error; + NSString* n1ql = [NSString stringWithFormat: + @"SELECT meta().id FROM _ WHERE meta().deleted AND meta().id = '%@'", docID]; + CBLQuery* q = [self.db createQuery: n1ql error: &error]; + Assert(q, @"Couldn't create query: %@", error); + CBLQueryResultSet* rs = [q execute: &error]; + Assert(rs, @"Query failed: %@", error); + return rs.allResults.count; +} + +- (void) testSetExpirationThenDeletionAfterwards { NSError* error; XCTestExpectation* expectation = [self expectationWithDescription: @"Document expiry test"]; @@ -516,73 +519,75 @@ - (void) testSetExpirationOnDeletedDocument { AssertEqualObjects(change.documentID, doc.id); AssertNil([change.collection documentWithID: change.documentID error: &err]); if (count == 2) { - CBLDocument* purgedDoc = [[CBLDocument alloc] initWithCollection: self.defaultCollection - documentID: doc.id - includeDeleted: YES - error: nil]; - AssertNil(purgedDoc); [expectation fulfill]; } }]; - // Delete doc - Assert([self.defaultCollection deleteDocument: doc error: &error]); - AssertNil(error); - AssertNil([self.defaultCollection documentWithID: doc.id error: &error]); - // Set expiry - NSDate* expiryDate = [NSDate dateWithTimeIntervalSinceNow: 1.0]; + NSDate* expiryDate = [NSDate dateWithTimeIntervalSinceNow: 2.0]; Assert([self.defaultCollection setDocumentExpirationWithID: doc.id expiration: expiryDate error: &error]); AssertNil(error); + // Delete doc + Assert([self.defaultCollection deleteDocument: doc error: &error]); + AssertNil(error); + AssertNil([self.defaultCollection documentWithID: doc.id error: &error]); + + // The deleted document (tombstone) should still exist: + AssertEqual([self deletedDocumentCount: doc.id], 1u); + // Wait for result [self waitForExpectationsWithTimeout: kExpTimeout handler: nil]; AssertEqual(count, 2); + // The expired document should be purged, not just deleted: + AssertEqual([self deletedDocumentCount: doc.id], 0u); + // Remove listener [token remove]; } -- (void) testPurgeImmediately { +- (void) testSetExpirationOnDeletedDocument { NSError* error; XCTestExpectation* expectation = [self expectationWithDescription: @"Document expiry test"]; // Create doc CBLDocument* doc = [self generateDocumentWithID: nil]; - AssertNil([self.defaultCollection getDocumentExpirationWithID: doc.id error: &error]); // Setup document change notification - __block NSDate* purgeTime; + __block int count = 0; id token = [self.defaultCollection addDocumentChangeListenerWithID: doc.id listener: ^(CBLDocumentChange *change) { NSError* err; + count++; AssertEqualObjects(change.documentID, doc.id); - if ([change.collection documentWithID: change.documentID error: &err] == nil) { - purgeTime = [NSDate date]; + AssertNil([change.collection documentWithID: change.documentID error: &err]); + if (count == 2) { [expectation fulfill]; } }]; + // Delete doc + Assert([self.defaultCollection deleteDocument: doc error: &error]); + AssertNil(error); + AssertNil([self.defaultCollection documentWithID: doc.id error: &error]); + // Set expiry - NSDate* begin = [NSDate date]; + NSDate* expiryDate = [NSDate dateWithTimeIntervalSinceNow: 1.0]; Assert([self.defaultCollection setDocumentExpirationWithID: doc.id - expiration: begin + expiration: expiryDate error: &error]); AssertNil(error); // Wait for result [self waitForExpectationsWithTimeout: kExpTimeout handler: nil]; + AssertEqual(count, 2); - /* - Validate. Delay inside the KeyStore::now() is in seconds, without milliseconds part. - Depending on the current milliseconds, we cannot gurantee, this will get purged exactly within - a second but in ~1 second. - */ - NSTimeInterval delta = [purgeTime timeIntervalSinceDate: begin]; - Assert(delta < 2); + // The expired document should be purged, not just deleted: + AssertEqual([self deletedDocumentCount: doc.id], 0u); // Remove listener [token remove]; diff --git a/Objective-C/Tests/DocumentTest.m b/Objective-C/Tests/DocumentTest.m index 277388e75..07b34e22c 100644 --- a/Objective-C/Tests/DocumentTest.m +++ b/Objective-C/Tests/DocumentTest.m @@ -18,10 +18,7 @@ // #import "CBLTestCase.h" - -#import "CBLBlob.h" -#import "CBLJSON.h" -#import "Foundation+CBL.h" +#import "CBLJSONUtil.h" #define kDocumentTestDate @"2017-01-01T00:00:00.000Z" #define kDocumentTestBlob @"i'm blob" @@ -44,7 +41,7 @@ - (void) populateData: (CBLMutableDocument*)doc { [doc setValue: @(1) forKey: @"one"]; [doc setValue: @(-1) forKey: @"minus_one"]; [doc setValue: @(1.1) forKey: @"one_dot_one"]; - [doc setValue: [CBLJSON dateWithJSONObject: kDocumentTestDate] forKey: @"date"]; + [doc setValue: [CBLJSONUtil dateFromJSONDateString: kDocumentTestDate] forKey: @"date"]; [doc setValue: [NSNull null] forKey: @"null"]; // Dictionary: @@ -599,7 +596,7 @@ - (void) testGetBoolean { - (void) testSetDate { CBLMutableDocument* doc = [self createDocument: @"doc1"]; NSDate* date = [NSDate date]; - NSString* dateStr = [CBLJSON JSONObjectWithDate: date]; + NSString* dateStr = [CBLJSONUtil jsonDateString: date]; Assert(dateStr.length > 0); [doc setValue: date forKey: @"date"]; [doc setDate: date forKey: @"date1"]; @@ -607,76 +604,76 @@ - (void) testSetDate { [self saveDocument: doc eval: ^(CBLDocument* d) { AssertEqualObjects([d valueForKey: @"date"], dateStr); AssertEqualObjects([d stringForKey: @"date"], dateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date"]], dateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date"]], dateStr); AssertEqualObjects([d valueForKey: @"date1"], dateStr); AssertEqualObjects([d stringForKey: @"date1"], dateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date1"]], dateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date1"]], dateStr); }]; // Update: NSDate* nuDate = [NSDate dateWithTimeInterval: 60.0 sinceDate: date]; - NSString* nuDateStr = [CBLJSON JSONObjectWithDate: nuDate]; + NSString* nuDateStr = [CBLJSONUtil jsonDateString: nuDate]; [doc setValue: nuDate forKey: @"date"]; [doc setDate: nuDate forKey: @"date1"]; [self saveDocument: doc eval: ^(CBLDocument* d) { AssertEqualObjects([d valueForKey: @"date"], nuDateStr); AssertEqualObjects([d stringForKey: @"date"], nuDateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date"]], nuDateStr); AssertEqualObjects([d valueForKey: @"date1"], nuDateStr); AssertEqualObjects([d stringForKey: @"date1"], nuDateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date1"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date1"]], nuDateStr); }]; // Get and update: doc = [[self.defaultCollection documentWithID: doc.id error: nil] toMutable]; nuDate = [NSDate dateWithTimeInterval: 120.0 sinceDate: date]; - nuDateStr = [CBLJSON JSONObjectWithDate: nuDate]; + nuDateStr = [CBLJSONUtil jsonDateString: nuDate]; [doc setValue: nuDate forKey: @"date"]; [doc setDate: nuDate forKey: @"date1"]; [self saveDocument: doc eval: ^(CBLDocument* d) { AssertEqualObjects([d valueForKey: @"date"], nuDateStr); AssertEqualObjects([d stringForKey: @"date"], nuDateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date"]], nuDateStr); AssertEqualObjects([d valueForKey: @"date1"], nuDateStr); AssertEqualObjects([d stringForKey: @"date1"], nuDateStr); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date1"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date1"]], nuDateStr); }]; } - (void) testSetDateInsideData { CBLMutableDocument* doc = [self createDocument: @"doc"]; NSDate* date = [NSDate date]; - NSString* dateStr = [CBLJSON JSONObjectWithDate: date]; + NSString* dateStr = [CBLJSONUtil jsonDateString: date]; Assert(dateStr.length > 0); [doc setData: @{@"time": date}]; [self saveDocument: doc eval: ^(CBLDocument* d) { - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"time"]], dateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"time"]], dateStr); }]; // Update: NSDate* nuDate = [NSDate dateWithTimeInterval: 60.0 sinceDate: date]; - NSString* nuDateStr = [CBLJSON JSONObjectWithDate: nuDate]; + NSString* nuDateStr = [CBLJSONUtil jsonDateString: nuDate]; [doc setData: @{@"time": nuDate}]; [self saveDocument: doc eval: ^(CBLDocument* d) { - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"time"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"time"]], nuDateStr); }]; // Get and update: doc = [[self.defaultCollection documentWithID: doc.id error: nil] toMutable]; nuDate = [NSDate dateWithTimeInterval: 120.0 sinceDate: date]; - nuDateStr = [CBLJSON JSONObjectWithDate: nuDate]; + nuDateStr = [CBLJSONUtil jsonDateString: nuDate]; [doc setData: @{@"time": nuDate}]; [self saveDocument: doc eval: ^(CBLDocument* d) { - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"time"]], nuDateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"time"]], nuDateStr); }]; } @@ -693,7 +690,7 @@ - (void) testGetDate { AssertNil([d dateForKey: @"minus_one"]); AssertNil([d dateForKey: @"one_dot_one"]); AssertNotNil([d dateForKey: @"date"]); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [d dateForKey: @"date"]], kDocumentTestDate); + AssertEqualObjects([CBLJSONUtil jsonDateString: [d dateForKey: @"date"]], kDocumentTestDate); AssertNil([d dateForKey: @"dict"]); AssertNil([d dateForKey: @"array"]); AssertNil([d dateForKey: @"blob"]); @@ -2042,8 +2039,8 @@ - (void) testDocumentToJSON { [self.defaultCollection saveDocument: mDoc error: &err]; CBLDocument* doc = [self.defaultCollection documentWithID: @"doc" error: nil]; - NSDictionary* jsonDict = [[doc toJSON] toJSONObj]; - AssertEqualObjects(jsonDict, [json toJSONObj]); + NSDictionary* jsonDict = [CBLJSONUtil jsonObjectFromString: [doc toJSON]]; + AssertEqualObjects(jsonDict, [CBLJSONUtil jsonObjectFromString: json]); AssertEqualObjects(jsonDict[@"name"], @"Rick Sanchez"); AssertEqualObjects(jsonDict[@"id"], @1); AssertEqualObjects(jsonDict[@"isAlive"], @YES); @@ -2062,8 +2059,8 @@ - (void) testDocumentToJSON { [self saveDocument: mDoc collection: self.defaultCollection]; doc = [self.defaultCollection documentWithID: @"doc" error: nil]; - jsonDict = [[doc toJSON] toJSONObj]; - NSMutableDictionary* mDict = [NSMutableDictionary dictionaryWithDictionary: [json toJSONObj]]; + jsonDict = [CBLJSONUtil jsonObjectFromString: [doc toJSON]]; + NSMutableDictionary* mDict = [NSMutableDictionary dictionaryWithDictionary: [CBLJSONUtil jsonObjectFromString: json]]; mDict[@"newKeyAppended"] = @"newValueAppended"; AssertEqualObjects(jsonDict, mDict); } @@ -2126,11 +2123,11 @@ - (void) testBlobToJSON { [self.db saveBlob: data error: &error]; - AssertEqualObjects([[data toJSON] toJSONObj], (@{kCBLTypeProperty: kCBLBlobType, + AssertEqualObjects([CBLJSONUtil jsonObjectFromString: [data toJSON]], (@{kCBLTypeProperty: kCBLBlobType, kCBLBlobContentTypeProperty: @"text/plain", kCBLBlobLengthProperty: @(data.length), kCBLBlobDigestProperty: data.digest})); - NSDictionary* dict = [[data toJSON] toJSONObj]; + NSDictionary* dict = [CBLJSONUtil jsonObjectFromString: [data toJSON]]; AssertEqualObjects(dict[kCBLTypeProperty], kCBLBlobType); AssertEqualObjects(dict[kCBLBlobContentTypeProperty], @"text/plain"); AssertEqualObjects(dict[kCBLBlobLengthProperty], @(data.length)); diff --git a/Objective-C/Tests/FragmentTest.m b/Objective-C/Tests/FragmentTest.m index 9c84f9cad..0b8f3d415 100644 --- a/Objective-C/Tests/FragmentTest.m +++ b/Objective-C/Tests/FragmentTest.m @@ -18,7 +18,7 @@ // #import "CBLTestCase.h" -#import "CBLJSON.h" +#import "CBLJSONUtil.h" @interface FragmentTest : CBLTestCase @end @@ -343,9 +343,9 @@ - (void) testGetFragmentFromDate { AssertNil(fragment.array); AssertNil(fragment.number); AssertNotNil(fragment.value); - AssertEqualObjects([CBLJSON JSONObjectWithDate: fragment.date], fragment.value); - AssertEqualObjects([CBLJSON JSONObjectWithDate: fragment.date], - [CBLJSON JSONObjectWithDate: date]); + AssertEqualObjects([CBLJSONUtil jsonDateString: fragment.date], fragment.value); + AssertEqualObjects([CBLJSONUtil jsonDateString: fragment.date], + [CBLJSONUtil jsonDateString: date]); XCTAssertEqualWithAccuracy([fragment.date timeIntervalSinceReferenceDate], [date timeIntervalSinceReferenceDate], 0.001); AssertEqual(fragment.integerValue, 0); @@ -480,8 +480,8 @@ - (void) testDictionaryFragmentSet { AssertEqual(d[@"int"].integerValue, 7); AssertEqual(d[@"float"].floatValue, 2.2f); AssertEqual(d[@"double"].doubleValue, 3.3); - AssertEqualObjects([CBLJSON JSONObjectWithDate: d[@"date"].date], - [CBLJSON JSONObjectWithDate: date]); + AssertEqualObjects([CBLJSONUtil jsonDateString: d[@"date"].date], + [CBLJSONUtil jsonDateString: date]); }]; } diff --git a/Objective-C/Tests/LogTest.m b/Objective-C/Tests/LogTest.m index 7b1d3edbb..34c62fffa 100644 --- a/Objective-C/Tests/LogTest.m +++ b/Objective-C/Tests/LogTest.m @@ -18,6 +18,13 @@ // #import "CBLTestCase.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + #import "CBLLog+Internal.h" #import "CBLTestCustomLogSink.h" diff --git a/Objective-C/Tests/MigrationTest.m b/Objective-C/Tests/MigrationTest.m index a4d73b922..2cee950ca 100644 --- a/Objective-C/Tests/MigrationTest.m +++ b/Objective-C/Tests/MigrationTest.m @@ -37,9 +37,11 @@ - (void)testMigration { Assert([manager copyItemAtPath: path toPath: copiedPath error: &error], @"Couldn't copy database: %@", error); - ++gC4ExpectExceptions; - CBLDatabase* database = [[CBLDatabase alloc] initWithName: @"iosdb" config: self.db.config error: &error]; - --gC4ExpectExceptions; + __block CBLDatabase* database; + __block NSError* openError; + [self ignoreExceptionBreakPoint: ^{ + database = [[CBLDatabase alloc] initWithName: @"iosdb" config: self.db.config error: &openError]; + }]; Assert(database); CBLDocument* doc1 = [[database defaultCollection: &error] documentWithID: @"doc1" error: &error]; diff --git a/Objective-C/Tests/MiscCppTest.mm b/Objective-C/Tests/MiscCppTest.mm index 3945d3ff2..3e453564a 100644 --- a/Objective-C/Tests/MiscCppTest.mm +++ b/Objective-C/Tests/MiscCppTest.mm @@ -18,6 +18,13 @@ // #import "CBLTestCase.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + #import "CBLStatus.h" @interface MiscCppTest : CBLTestCase diff --git a/Objective-C/Tests/MiscTest.m b/Objective-C/Tests/MiscTest.m index ef9009afc..703c68e36 100644 --- a/Objective-C/Tests/MiscTest.m +++ b/Objective-C/Tests/MiscTest.m @@ -18,6 +18,13 @@ // #import "CBLTestCase.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + #import "CBLJSON.h" #import "CBLMisc.h" #import "CBLParseDate.h" diff --git a/Objective-C/Tests/MultipeerReplicatorTest.m b/Objective-C/Tests/MultipeerReplicatorTest.m index 99fd83169..5c8fcb732 100644 --- a/Objective-C/Tests/MultipeerReplicatorTest.m +++ b/Objective-C/Tests/MultipeerReplicatorTest.m @@ -7,6 +7,13 @@ // #import "MultipeerReplicatorTest.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + #import "CBLDatabase+Internal.h" #import "CBLTLSIdentity+Internal.h" diff --git a/Objective-C/Tests/NotificationTest.m b/Objective-C/Tests/NotificationTest.m index 650309bde..bea25a323 100644 --- a/Objective-C/Tests/NotificationTest.m +++ b/Objective-C/Tests/NotificationTest.m @@ -18,7 +18,6 @@ // #import "CBLTestCase.h" -#import "CBLDatabase+Internal.h" @interface NotificationTest : CBLTestCase diff --git a/Objective-C/Tests/PredictiveQueryTest+CoreML.m b/Objective-C/Tests/PredictiveQueryTest+CoreML.m index e3dc87ffb..7593fa139 100644 --- a/Objective-C/Tests/PredictiveQueryTest+CoreML.m +++ b/Objective-C/Tests/PredictiveQueryTest+CoreML.m @@ -17,7 +17,9 @@ // #import "CBLTestCase.h" +#ifndef CBL_BINARY_TEST #import "CBLCoreMLPredictiveModel+Internal.h" +#endif @interface PredictiveQueryWithCoreMLTest : CBLTestCase @@ -242,6 +244,57 @@ - (void) testOpenFaceModel { [CBLDatabase.prediction unregisterModelWithName: @"OpenFace"]; } +// Note: Download MobileNet.mlmodel from https://developer.apple.com/documentation/vision/classifying_images_with_vision_and_core_ml +// and put it at Objective-C/Tests/Support/mlmodels/MobileNet +- (void) testInputOutputTransformer { + CBLCoreMLPredictiveModel* model = [self model: @"MobileNet/MobileNet" mustExist: NO]; + if (!model) + return; + + model.inputTransformer = ^CBLDictionary*(CBLDictionary *input) { + CBLMutableDictionary* transformed = [[CBLMutableDictionary alloc] init]; + [transformed setValue: [input valueForKey: @"photo"] forKey: @"image"]; + return transformed; + }; + + model.outputTransformer = ^CBLDictionary*(CBLDictionary *output) { + if (output) { + CBLMutableDictionary* transformed = [[CBLMutableDictionary alloc] init]; + NSString* label = [output valueForKey: @"classLabel"]; + [transformed setValue: label forKey: @"label"]; + + CBLDictionary* probs = [output valueForKey: @"classLabelProbs"]; + [transformed setValue: [probs valueForKey: label] forKey: @"prob"]; + return transformed; + } + return output; + }; + [CBLDatabase.prediction registerModel: model withName: @"MobileNet"]; + + [self createDocumentWithImageAtPath: @"mlmodels/MobileNet/cat.jpg"]; + + NSDictionary* input = @{ @"photo": EXPR_PROP(@"image") }; + CBLQuery *q = [CBLQueryBuilder select: @[SEL_EXPR(PREDICTION(@"MobileNet", EXPR_VAL(input)))] + from: kDATA_SRC_DB]; + uint64_t numRows = [self verifyQuery: q randomAccess: NO + test: ^(uint64_t n, CBLQueryResult *r) + { + CBLDictionary* pred = [r dictionaryAtIndex: 0]; + NSString* label = [[pred stringForKey: @"label"] lowercaseString]; + Assert([label rangeOfString: @"cat"].location != NSNotFound); + Assert([pred doubleForKey: @"prob"] > 0.0); + }]; + AssertEqual(numRows, 1); + + [CBLDatabase.prediction unregisterModelWithName: @"MobileNet"]; +} + +#pragma mark - Internal + +// White-box tests of the internal CoreML conversion helpers; excluded from +// the binary tests. +#ifndef CBL_BINARY_TEST + - (void) testBasicDataConversion { NSDictionary* dictData = @{@"name": @"Daniel", @"number": @(1)}; CBLMutableDictionary* dict = [[CBLMutableDictionary alloc] initWithData: dictData]; @@ -355,50 +408,7 @@ - (void) testPixelBufferDataConversion { AssertEqualObjects(blob2.contentType, @"image/png"); } -// Note: Download MobileNet.mlmodel from https://developer.apple.com/documentation/vision/classifying_images_with_vision_and_core_ml -// and put it at Objective-C/Tests/Support/mlmodels/MobileNet -- (void) testInputOutputTransformer { - CBLCoreMLPredictiveModel* model = [self model: @"MobileNet/MobileNet" mustExist: NO]; - if (!model) - return; - - model.inputTransformer = ^CBLDictionary*(CBLDictionary *input) { - CBLMutableDictionary* transformed = [[CBLMutableDictionary alloc] init]; - [transformed setValue: [input valueForKey: @"photo"] forKey: @"image"]; - return transformed; - }; - - model.outputTransformer = ^CBLDictionary*(CBLDictionary *output) { - if (output) { - CBLMutableDictionary* transformed = [[CBLMutableDictionary alloc] init]; - NSString* label = [output valueForKey: @"classLabel"]; - [transformed setValue: label forKey: @"label"]; - - CBLDictionary* probs = [output valueForKey: @"classLabelProbs"]; - [transformed setValue: [probs valueForKey: label] forKey: @"prob"]; - return transformed; - } - return output; - }; - [CBLDatabase.prediction registerModel: model withName: @"MobileNet"]; - - [self createDocumentWithImageAtPath: @"mlmodels/MobileNet/cat.jpg"]; - - NSDictionary* input = @{ @"photo": EXPR_PROP(@"image") }; - CBLQuery *q = [CBLQueryBuilder select: @[SEL_EXPR(PREDICTION(@"MobileNet", EXPR_VAL(input)))] - from: kDATA_SRC_DB]; - uint64_t numRows = [self verifyQuery: q randomAccess: NO - test: ^(uint64_t n, CBLQueryResult *r) - { - CBLDictionary* pred = [r dictionaryAtIndex: 0]; - NSString* label = [[pred stringForKey: @"label"] lowercaseString]; - Assert([label rangeOfString: @"cat"].location != NSNotFound); - Assert([pred doubleForKey: @"prob"] > 0.0); - }]; - AssertEqual(numRows, 1); - - [CBLDatabase.prediction unregisterModelWithName: @"MobileNet"]; -} +#endif #pragma clang diagnostic pop diff --git a/Objective-C/Tests/PredictiveQueryTest.m b/Objective-C/Tests/PredictiveQueryTest.m index 7d1f8332f..207782908 100644 --- a/Objective-C/Tests/PredictiveQueryTest.m +++ b/Objective-C/Tests/PredictiveQueryTest.m @@ -17,7 +17,7 @@ // #import "CBLTestCase.h" -#import "CBLJSON.h" +#import "CBLJSONUtil.h" @interface CBLTestPredictiveModel: NSObject @@ -154,7 +154,7 @@ - (void) testPredictionInputOutput { // Create prediction function input: NSDate* date = [NSDate date]; - NSString* dateStr = [CBLJSON JSONObjectWithDate: date]; + NSString* dateStr = [CBLJSONUtil jsonDateString: date]; NSDictionary* dict = @{ // Literal: @@ -212,7 +212,7 @@ - (void) testPredictionInputOutput { AssertEqual([pred booleanForKey: @"boolean_true"], YES); AssertEqual([pred booleanForKey: @"boolean_false"], NO); AssertEqualObjects([pred stringForKey: @"string"], @"hello"); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [pred dateForKey: @"date"]], dateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [pred dateForKey: @"date"]], dateStr); AssertEqualObjects([pred valueForKey: @"null"], [NSNull null]); AssertEqualObjects([[pred dictionaryForKey: @"dict"] toDictionary], @{@"foo": @"bar"}); AssertEqualObjects([[pred arrayForKey: @"array"] toArray], (@[@"1", @"2", @"3"])); @@ -222,7 +222,7 @@ - (void) testPredictionInputOutput { AssertEqual([pred doubleForKey: @"expr_value_number2"], 20.1); AssertEqual([pred booleanForKey: @"expr_value_boolean"], YES); AssertEqualObjects([pred stringForKey: @"expr_value_string"], @"hi"); - AssertEqualObjects([CBLJSON JSONObjectWithDate: [pred dateForKey: @"expr_value_date"]], dateStr); + AssertEqualObjects([CBLJSONUtil jsonDateString: [pred dateForKey: @"expr_value_date"]], dateStr); AssertEqualObjects([pred valueForKey: @"expr_value_null"], [NSNull null]); AssertEqualObjects([[pred dictionaryForKey: @"expr_value_dict"] toDictionary], @{@"ping": @"pong"}); AssertEqualObjects([[pred arrayForKey: @"expr_value_array"] toArray], (@[@"4", @"5", @"6"])); diff --git a/Objective-C/Tests/QueryTest+Main.m b/Objective-C/Tests/QueryTest+Main.m index 912f1e8ad..43ef0aa96 100644 --- a/Objective-C/Tests/QueryTest+Main.m +++ b/Objective-C/Tests/QueryTest+Main.m @@ -18,22 +18,22 @@ // #import "QueryTest.h" +#import "CBLJSONUtil.h" +#ifndef CBL_BINARY_TEST #import "CBLQuery+Internal.h" #import "CBLQuery+JSON.h" -#import "CBLQueryBuilder.h" -#import "CBLQuerySelectResult.h" -#import "CBLQueryDataSource.h" -#import "CBLQueryOrdering.h" #import "CBLQueryResultArray.h" #import "CBLValueExpression.h" #import "CBLQueryExpression+Internal.h" #import "CBLUnaryExpression.h" -#import "Foundation+CBL.h" +#endif #import "CollectionUtils.h" #ifdef DEBUG +#ifndef CBL_BINARY_TEST #import "CBLQueryObserver.h" #endif +#endif @interface QueryTest_Main : QueryTest @@ -42,7 +42,7 @@ @interface QueryTest_Main : QueryTest @implementation QueryTest_Main - (void) tearDown { -#ifdef DEBUG +#if defined(DEBUG) && !defined(CBL_BINARY_TEST) [CBLQueryObserver setC4QueryObserverCallbackDelayInterval: 0.0]; #endif [super tearDown]; @@ -313,7 +313,6 @@ - (void) testWhereFullTextFunctionMatch { AssertEqual(numRows, 2u); } - #pragma mark - Select - (void) testSelectDistinct { @@ -478,7 +477,6 @@ - (void) testDatabaseAliasWithMultipleSources { AssertEqual(numRows, 1u); } - #pragma mark - OrderBy/GroupBy - (void) testOrderBy { @@ -1140,8 +1138,7 @@ - (void) testQuantifiedOperators { satisfies: [LIKE equalTo: [CBLQueryExpression string: @"climbing"]]]]; NSLog(@"%@", [q explain: nil]); - - + NSArray* expected = @[@"doc-017", @"doc-021", @"doc-023", @"doc-045", @"doc-060"]; uint64_t numRows = [self verifyQuery: q randomAccess: YES @@ -1221,36 +1218,6 @@ - (void) testQuantifiedOperatorVariableKeyPath { #pragma mark - Collation -- (void) testGenerateJSONCollation { - NSArray* collations = - @[[CBLQueryCollation asciiWithIgnoreCase: NO], - [CBLQueryCollation asciiWithIgnoreCase: YES], - [CBLQueryCollation unicodeWithLocale: nil ignoreCase: NO ignoreAccents: NO], - [CBLQueryCollation unicodeWithLocale: nil ignoreCase: YES ignoreAccents: NO], - [CBLQueryCollation unicodeWithLocale: nil ignoreCase: YES ignoreAccents: YES], - [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: NO ignoreAccents: NO], - [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: YES ignoreAccents: NO], - [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: YES ignoreAccents: YES]]; - - NSString* deviceLocale = [NSLocale currentLocale].localeIdentifier; - NSArray* expected = - @[ - @{@"UNICODE": @(NO), @"LOCALE": [NSNull null] ,@"CASE": @(YES), @"DIAC": @(YES)}, - @{@"UNICODE": @(NO), @"LOCALE": [NSNull null] ,@"CASE": @(NO) , @"DIAC": @(YES)}, - @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(YES), @"DIAC": @(YES)}, - @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(NO), @"DIAC": @(YES)}, - @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(NO), @"DIAC": @(NO)}, - @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(YES), @"DIAC": @(YES)}, - @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(NO), @"DIAC": @(YES)}, - @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(NO), @"DIAC": @(NO)} - ]; - - NSInteger i = 0; - for (CBLQueryCollation* c in collations) { - AssertEqualObjects([c asJSON], expected[i++]); - } -} - - (void) testUnicodeCollationWithLocale { NSArray* letters = @[@"B", @"A", @"Z", @"Å"]; for (NSString* letter in letters) { @@ -1599,60 +1566,6 @@ - (void) testMissingValue { AssertEqualObjects([r toDictionary], (@{@"name": @"Scott", @"address": [NSNull null]})); } -- (void) testJSONEncoding { - NSError* error; - CBLMutableDocument* doc1 = [[CBLMutableDocument alloc] init]; - [doc1 setValue: @"string" forKey: @"string"]; - Assert([self.defaultCollection saveDocument: doc1 error: &error], @"Error when creating a document: %@", error); - - NSData* json; - { - CBLQueryExpression* expr = [[CBLQueryExpression property: @"string"] is: - [CBLQueryExpression string: @"string"]]; - CBLQuery* q = [CBLQueryBuilder select: @[kDOCID] - from: kDATA_SRC_DB - where: expr]; - json = q.json; - Assert(json); - } - - // Reconstitute query from JSON data: - CBLQuery* q = [[CBLQuery alloc] initWithDatabase: _db json: json]; - Assert(q); - AssertEqualObjects(q.json, json); - - // Now test the reconstituted query: - uint64_t numRows = [self verifyQuery: q - randomAccess: YES - test: ^(uint64_t n, CBLQueryResult* r) { - NSError* err; - CBLDocument* doc = [self.defaultCollection documentWithID: [r valueAtIndex: 0] error: &err]; - AssertEqualObjects(doc.id, doc1.id); - AssertEqualObjects([doc valueForKey: @"string"], @"string"); - }]; - AssertEqual(numRows, 1u); -} - -- (void) testQueryResultArray { - [self loadNumbers: 5]; - CBLQuery* q = [CBLQueryBuilder select: @[kDOCID] - from: kDATA_SRC_DB - where: nil - orderBy: @[[CBLQueryOrdering property: @"number1"]]]; - NSError* error; - CBLQueryResultSet* rs = [q execute: &error]; - Assert(rs, @"Query failed: %@", error); - - NSArray* allObjects = rs.allObjects; - CBLQueryResultArray* array = [[CBLQueryResultArray alloc] initWithResultSet: rs - count: allObjects.count]; - AssertEqual(array.count, allObjects.count); - Assert(![[array mutableCopy] isEqual: array]); - AssertEqual([[array objectAtIndex: 0] valueForKey: @"id"], - [[allObjects objectAtIndex: 0] valueForKey: @"id"]); - AssertEqual([[array objectAtIndex: 4] valueForKey: @"id"], - [[allObjects objectAtIndex: 4] valueForKey: @"id"]); -} #pragma mark - toJSON @@ -1682,89 +1595,14 @@ - (void) testQueryJSON { Assert(rs, @"Query failed: %@", error); CBLQueryResult* r = rs.allResults.firstObject; - NSMutableDictionary* temp = [[json toJSONObj] mutableCopy]; + NSMutableDictionary* temp = [[CBLJSONUtil jsonObjectFromString: json] mutableCopy]; temp[@"id"] = @"doc"; - AssertEqualObjects([[r toJSON] toJSONObj], temp); + AssertEqualObjects([CBLJSONUtil jsonObjectFromString: [r toJSON]], temp); } #pragma mark - Value Expression -- (void) testValueExpressionUnsupportedValueType { - NSData* data = [[NSData alloc] init]; - [self expectException: NSInternalInconsistencyException in:^{ - CBLValueExpression* v = [[CBLValueExpression alloc] initWithValue: data]; - AssertNil(v); - }]; -} - -- (void) testValueExpression { - CBLValueExpression* v = [[CBLValueExpression alloc] initWithValue: nil]; - AssertEqualObjects([v asJSON], [NSNull null]); - - v = [[CBLValueExpression alloc] initWithValue: [NSDate dateWithTimeIntervalSince1970: 1]]; - AssertEqualObjects([v asJSON], @"1970-01-01T00:00:01.000Z"); - - v = [[CBLValueExpression alloc] initWithValue: [NSDictionary dictionaryWithObjectsAndKeys: - @"value101", @"key101", nil]]; - AssertEqualObjects([v asJSON], @{@"key101": @"value101"}); - - NSArray* expectedResult= @[ @"[]", @"item1", @"item2" ]; - v = [[CBLValueExpression alloc] initWithValue: [NSArray arrayWithObjects: - @"item1", @"item2", nil]]; - AssertEqualObjects([v asJSON], expectedResult); - - v = [[CBLValueExpression alloc] initWithValue: [CBLQueryExpression number: @21]]; - AssertEqualObjects([v asJSON], @21); -} - -- (void) testUnaryQueryExpression { - NSDate* nw = [NSDate date]; - CBLMutableDocument* doc1 = [self createDocument]; - [doc1 setDate: nw forKey: @"now"]; - [self saveDocument: doc1 collection: self.defaultCollection]; - - CBLMutableDocument* doc2 = [self createDocument]; - [self saveDocument: doc2 collection: self.defaultCollection]; - - CBLUnaryExpression* notNull; - CBLUnaryExpression* notMiss; - CBLQueryExpression* propNow = [CBLQueryExpression property: @"now"]; - notNull = [[CBLUnaryExpression alloc] initWithExpression: propNow type: CBLUnaryTypeNotNull]; - notMiss = [[CBLUnaryExpression alloc] initWithExpression: propNow type: CBLUnaryTypeNotMissing]; - - CBLQuery* q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] - from: kDATA_SRC_DB - where: [notNull orExpression: notMiss]]; - uint64_t rows = [self verifyQuery: q randomAccess: YES - test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { - NSDate* savedDate = [[result dictionaryAtIndex: 0] - dateForKey: @"now"]; - Assert([nw timeIntervalSinceDate: savedDate] < 0.001); - }]; - AssertEqual(rows, 1u); - - // check same result is produced with isValued. - q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] - from: kDATA_SRC_DB - where: [propNow isValued]]; - rows = [self verifyQuery: q randomAccess: YES - test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { - NSDate* savedDate = [[result dictionaryAtIndex: 0] dateForKey: @"now"]; - Assert([nw timeIntervalSinceDate: savedDate] < 0.001); - }]; - AssertEqual(rows, 1u); - - q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] - from: kDATA_SRC_DB - where: [propNow isValued]]; - rows = [self verifyQuery: q randomAccess: YES - test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { - NSDate* savedDate = [[result dictionaryAtIndex: 0] dateForKey: @"now"]; - Assert([nw timeIntervalSinceDate: savedDate] < 0.001); - }]; - AssertEqual(rows, 1u); -} #pragma mark - N1QL @@ -1872,8 +1710,7 @@ - (void) testLiveQueryNoUpdate { // This change will not affect the query results because 'number1 < 10' is not true. [self createDocNumbered: 111 of: 100]; - - + // Wait 2 seconds, then fulfil the expectation: XCTestExpectation *x = [self expectationWithDescription: @"Timeout"]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), @@ -2162,6 +1999,145 @@ - (void) testLiveQueryNoChangesNotifiedAfterRemoveListenerToken { #ifdef DEBUG +#endif + +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testJSONEncoding { + NSError* error; + CBLMutableDocument* doc1 = [[CBLMutableDocument alloc] init]; + [doc1 setValue: @"string" forKey: @"string"]; + Assert([self.defaultCollection saveDocument: doc1 error: &error], @"Error when creating a document: %@", error); + + NSData* json; + { + CBLQueryExpression* expr = [[CBLQueryExpression property: @"string"] is: + [CBLQueryExpression string: @"string"]]; + CBLQuery* q = [CBLQueryBuilder select: @[kDOCID] + from: kDATA_SRC_DB + where: expr]; + json = q.json; + Assert(json); + } + + // Reconstitute query from JSON data: + CBLQuery* q = [[CBLQuery alloc] initWithDatabase: _db json: json]; + Assert(q); + AssertEqualObjects(q.json, json); + + // Now test the reconstituted query: + uint64_t numRows = [self verifyQuery: q + randomAccess: YES + test: ^(uint64_t n, CBLQueryResult* r) { + NSError* err; + CBLDocument* doc = [self.defaultCollection documentWithID: [r valueAtIndex: 0] error: &err]; + AssertEqualObjects(doc.id, doc1.id); + AssertEqualObjects([doc valueForKey: @"string"], @"string"); + }]; + AssertEqual(numRows, 1u); +} + +- (void) testQueryResultArray { + [self loadNumbers: 5]; + CBLQuery* q = [CBLQueryBuilder select: @[kDOCID] + from: kDATA_SRC_DB + where: nil + orderBy: @[[CBLQueryOrdering property: @"number1"]]]; + NSError* error; + CBLQueryResultSet* rs = [q execute: &error]; + Assert(rs, @"Query failed: %@", error); + + NSArray* allObjects = rs.allObjects; + CBLQueryResultArray* array = [[CBLQueryResultArray alloc] initWithResultSet: rs + count: allObjects.count]; + AssertEqual(array.count, allObjects.count); + Assert(![[array mutableCopy] isEqual: array]); + AssertEqual([[array objectAtIndex: 0] valueForKey: @"id"], + [[allObjects objectAtIndex: 0] valueForKey: @"id"]); + AssertEqual([[array objectAtIndex: 4] valueForKey: @"id"], + [[allObjects objectAtIndex: 4] valueForKey: @"id"]); +} + +- (void) testValueExpressionUnsupportedValueType { + NSData* data = [[NSData alloc] init]; + [self expectException: NSInternalInconsistencyException in:^{ + CBLValueExpression* v = [[CBLValueExpression alloc] initWithValue: data]; + AssertNil(v); + }]; +} + +- (void) testValueExpression { + CBLValueExpression* v = [[CBLValueExpression alloc] initWithValue: nil]; + AssertEqualObjects([v asJSON], [NSNull null]); + + v = [[CBLValueExpression alloc] initWithValue: [NSDate dateWithTimeIntervalSince1970: 1]]; + AssertEqualObjects([v asJSON], @"1970-01-01T00:00:01.000Z"); + + v = [[CBLValueExpression alloc] initWithValue: [NSDictionary dictionaryWithObjectsAndKeys: + @"value101", @"key101", nil]]; + AssertEqualObjects([v asJSON], @{@"key101": @"value101"}); + + NSArray* expectedResult= @[ @"[]", @"item1", @"item2" ]; + v = [[CBLValueExpression alloc] initWithValue: [NSArray arrayWithObjects: + @"item1", @"item2", nil]]; + AssertEqualObjects([v asJSON], expectedResult); + + v = [[CBLValueExpression alloc] initWithValue: [CBLQueryExpression number: @21]]; + AssertEqualObjects([v asJSON], @21); +} + +- (void) testUnaryQueryExpression { + NSDate* nw = [NSDate date]; + CBLMutableDocument* doc1 = [self createDocument]; + [doc1 setDate: nw forKey: @"now"]; + [self saveDocument: doc1 collection: self.defaultCollection]; + + CBLMutableDocument* doc2 = [self createDocument]; + [self saveDocument: doc2 collection: self.defaultCollection]; + + CBLUnaryExpression* notNull; + CBLUnaryExpression* notMiss; + CBLQueryExpression* propNow = [CBLQueryExpression property: @"now"]; + notNull = [[CBLUnaryExpression alloc] initWithExpression: propNow type: CBLUnaryTypeNotNull]; + notMiss = [[CBLUnaryExpression alloc] initWithExpression: propNow type: CBLUnaryTypeNotMissing]; + + CBLQuery* q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] + from: kDATA_SRC_DB + where: [notNull orExpression: notMiss]]; + uint64_t rows = [self verifyQuery: q randomAccess: YES + test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { + NSDate* savedDate = [[result dictionaryAtIndex: 0] + dateForKey: @"now"]; + Assert([nw timeIntervalSinceDate: savedDate] < 0.001); + }]; + AssertEqual(rows, 1u); + + // check same result is produced with isValued. + q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] + from: kDATA_SRC_DB + where: [propNow isValued]]; + + rows = [self verifyQuery: q randomAccess: YES + test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { + NSDate* savedDate = [[result dictionaryAtIndex: 0] dateForKey: @"now"]; + Assert([nw timeIntervalSinceDate: savedDate] < 0.001); + }]; + AssertEqual(rows, 1u); + + q = [CBLQueryBuilder select: @[[CBLQuerySelectResult all]] + from: kDATA_SRC_DB + where: [propNow isValued]]; + rows = [self verifyQuery: q randomAccess: YES + test: ^(uint64_t n, CBLQueryResult * _Nonnull result) { + NSDate* savedDate = [[result dictionaryAtIndex: 0] dateForKey: @"now"]; + Assert([nw timeIntervalSinceDate: savedDate] < 0.001); + }]; + AssertEqual(rows, 1u); +} + // CBL-5659 : Invalidated context may be used in query observer callback // This tests that the callback will not be called without a crash. - (void) testLiveQueryNoDelayedChangesNotifiedAfterRemoveListenerToken { @@ -2209,6 +2185,36 @@ - (void) testLiveQueryNoDelayedChangesNotifiedAfterRemoveListenerToken { [self waitForExpectations: @[noChangedExp] timeout: kExpTimeout]; } +- (void) testGenerateJSONCollation { + NSArray* collations = + @[[CBLQueryCollation asciiWithIgnoreCase: NO], + [CBLQueryCollation asciiWithIgnoreCase: YES], + [CBLQueryCollation unicodeWithLocale: nil ignoreCase: NO ignoreAccents: NO], + [CBLQueryCollation unicodeWithLocale: nil ignoreCase: YES ignoreAccents: NO], + [CBLQueryCollation unicodeWithLocale: nil ignoreCase: YES ignoreAccents: YES], + [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: NO ignoreAccents: NO], + [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: YES ignoreAccents: NO], + [CBLQueryCollation unicodeWithLocale: @"en" ignoreCase: YES ignoreAccents: YES]]; + + NSString* deviceLocale = [NSLocale currentLocale].localeIdentifier; + NSArray* expected = + @[ + @{@"UNICODE": @(NO), @"LOCALE": [NSNull null] ,@"CASE": @(YES), @"DIAC": @(YES)}, + @{@"UNICODE": @(NO), @"LOCALE": [NSNull null] ,@"CASE": @(NO) , @"DIAC": @(YES)}, + @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(YES), @"DIAC": @(YES)}, + @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(NO), @"DIAC": @(YES)}, + @{@"UNICODE": @(YES), @"LOCALE": deviceLocale ,@"CASE": @(NO), @"DIAC": @(NO)}, + @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(YES), @"DIAC": @(YES)}, + @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(NO), @"DIAC": @(YES)}, + @{@"UNICODE": @(YES), @"LOCALE": @"en" ,@"CASE": @(NO), @"DIAC": @(NO)} + ]; + + NSInteger i = 0; + for (CBLQueryCollation* c in collations) { + AssertEqualObjects([c asJSON], expected[i++]); + } +} + #endif @end diff --git a/Objective-C/Tests/ReplicatorTest+Backgrounding.m b/Objective-C/Tests/ReplicatorTest+Backgrounding.m new file mode 100644 index 000000000..60d005327 --- /dev/null +++ b/Objective-C/Tests/ReplicatorTest+Backgrounding.m @@ -0,0 +1,334 @@ +// +// ReplicatorTest+Backgrounding.m +// CouchbaseLite +// +// Copyright (c) 2026 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "ReplicatorTest.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + + +#if TARGET_OS_IPHONE +#import "CBLBlockConflictResolver.h" +#import "CBLReplicator+Backgrounding.h" +#import "CBLReplicator+Internal.h" + +/** White-box tests that drive the replicator's internal app-backgrounding and + suspension hooks; not part of the binary test suite. */ +@interface ReplicatorTest_Backgrounding : ReplicatorTest + +@end + +@implementation ReplicatorTest_Backgrounding { + id _target; +} + +- (void) setUp { + [super setUp]; + _target = [[CBLDatabaseEndpoint alloc] initWithDatabase: self.otherDB]; +} + +- (void) tearDown { + _target = nil; + [super tearDown]; +} + +- (void) testSwitchBackgroundForeground { + + id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; + CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; + + static NSInteger numRounds = 10; + + NSMutableArray* foregroundExps = [NSMutableArray arrayWithCapacity: numRounds + 1]; + NSMutableArray* backgroundExps = [NSMutableArray arrayWithCapacity: numRounds]; + for (NSInteger i = 0; i < numRounds; i++) { + [foregroundExps addObject: [self allowOverfillExpectationWithDescription: @"Foregrounding"]]; + [backgroundExps addObject: [self expectationWithDescription: @"Backgrounding"]]; + } + [foregroundExps addObject: [self allowOverfillExpectationWithDescription: @"Foregrounding"]]; + + __block NSInteger backgroundCount = 0; + __block NSInteger foregroundCount = 0; + + XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; + + id token = [r addChangeListener: ^(CBLReplicatorChange* change) { + AssertNil(change.status.error); + if (change.status.activity == kCBLReplicatorIdle) { + if (foregroundCount <= numRounds) + [foregroundExps[foregroundCount++] fulfill]; + } else if (change.status.activity == kCBLReplicatorOffline) { + [backgroundExps[backgroundCount++] fulfill]; + } else if (change.status.activity == kCBLReplicatorStopped) { + [stopped fulfill]; + } + }]; + + [r start]; + [self waitForExpectations: @[foregroundExps[0]] timeout: kExpTimeout]; + + for (int i = 0; i < numRounds; i++) { + [r appBackgrounding]; + [self waitForExpectations: @[backgroundExps[i]] timeout: kExpTimeout]; + Assert(r.conflictResolutionSuspended); + + [r appForegrounding]; + [self waitForExpectations: @[foregroundExps[i+1]] timeout: kExpTimeout]; + AssertFalse(r.conflictResolutionSuspended); + } + + [r stop]; + [self waitForExpectations: @[stopped] timeout: kExpTimeout]; + + AssertEqual(foregroundCount, numRounds + 1); + AssertEqual(backgroundCount, numRounds); + + [token remove]; + r = nil; +} + +- (void) testSwitchToForegroundImmediately { + id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; + CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; + + XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"idle"]; + XCTestExpectation* foregroundExp = [self allowOverfillExpectationWithDescription: @"Foregrounding"]; + XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; + + __block int idleCount = 0; + id token = [r addChangeListener: ^(CBLReplicatorChange* change) { + AssertNil(change.status.error); + if (change.status.activity == kCBLReplicatorIdle) { + if (idleCount++) + [foregroundExp fulfill]; + else + [idle fulfill]; + } else if (change.status.activity == kCBLReplicatorStopped) { + [stopped fulfill]; + } + }]; + + [r start]; + [self waitForExpectations: @[idle] timeout: kExpTimeout]; + + // Switch to background and immediately comes back to foreground + [r setSuspended: YES]; + [r setSuspended: NO]; + + [self waitForExpectations: @[foregroundExp] timeout: kExpTimeout]; + + [r stop]; + [self waitForExpectations: @[stopped] timeout: kExpTimeout]; + + [token remove]; + r = nil; +} + +- (void) testBackgroundingWhenStopping { + id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; + CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; + + __block BOOL foregrounding = NO; + + XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"Idle after starting"]; + XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; + XCTestExpectation* done = [self expectationWithDescription: @"Done"]; + + id token = [r addChangeListener: ^(CBLReplicatorChange* change) { + Assert(!foregrounding); + AssertNil(change.status.error); + Assert(change.status.activity != kCBLReplicatorOffline); + + if (change.status.activity == kCBLReplicatorIdle) { + [idle fulfill]; + } else if (change.status.activity == kCBLReplicatorStopped) { + [stopped fulfill]; + } + }]; + + [r start]; + [self waitForExpectations: @[idle] timeout: kExpTimeout]; + + [r stop]; + + // This shouldn't prevent the replicator to stop: + [r appBackgrounding]; + [self waitForExpectations: @[stopped] timeout: kExpTimeout]; + + // This shouldn't wake up the replicator: + foregrounding = YES; + [r appForegrounding]; + + // Wait for 0.3 seconds to ensure no more changes notified and cause !foregrounding to fail: + id block = [NSBlockOperation blockOperationWithBlock: ^{ [done fulfill]; }]; + [NSTimer scheduledTimerWithTimeInterval: 0.3 + target: block + selector: @selector(main) userInfo: nil repeats: NO]; + [self waitForExpectations: @[done] timeout: kExpTimeout]; + + [token remove]; + r = nil; +} + +- (void) testBackgroundingDuringDataTransfer { + XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"idle-and-ready"]; + XCTestExpectation* busy = [self allowOverfillExpectationWithDescription: @"transferring data"]; + XCTestExpectation* offline = [self expectationWithDescription: @"app-in-background"]; + XCTestExpectation* stop = [self allowOverfillExpectationWithDescription: @"finish-transfer"]; + + // setup replicator + CBLReplicatorConfiguration* config = [self configWithTarget: _target type: kCBLReplicatorTypePush + continuous: YES]; + CBLReplicator* replicator = [[CBLReplicator alloc] initWithConfig: config]; + __block int busyCount = 0; + __block int idleCount = 0; + id token = [replicator addChangeListener: ^(CBLReplicatorChange* change) { + if (change.status.activity == kCBLReplicatorIdle) { + if (++idleCount == 1) + [idle fulfill]; + else if (change.status.progress.completed == change.status.progress.total) + [change.replicator stop]; + } else if (change.status.activity == kCBLReplicatorBusy) { + if (++busyCount == 1) + [busy fulfill]; + } else if (change.status.activity == kCBLReplicatorOffline) { + [offline fulfill]; + } else if (change.status.activity == kCBLReplicatorStopped) { + [stop fulfill]; + } + }]; + + // start and wait for idle + AssertEqual(self.otherDBDefaultCollection.count, 0); + [replicator start]; + [self waitForExpectations: @[idle] timeout: kExpTimeout]; + + // replicate a doc with blob, and wait for busy + NSError* error; + CBLMutableDocument* doc1 = [[CBLMutableDocument alloc] initWithID: @"doc1"]; + NSData* data = [self dataFromResource: @"image" ofType: @"jpg"]; + CBLBlob* blob = [[CBLBlob alloc] initWithContentType: @"image/jpg" data: data]; + [doc1 setBlob: blob forKey: @"blob"]; + Assert([self.defaultCollection saveDocument: doc1 error: &error]); + [self waitForExpectations: @[busy] timeout: kExpTimeout]; + + // background during the data transfer! + [replicator setSuspended: YES]; + [self waitForExpectations: @[offline] timeout: kExpTimeout]; + + // forground after 0.2 secs + [NSThread sleepForTimeInterval: 0.2]; + [replicator setSuspended: NO]; + + [self waitForExpectations: @[stop] timeout: kExpTimeout]; + [token remove]; + + // make sure the doc with blob transferred successfully! + AssertEqual(self.otherDBDefaultCollection.count, 1); + CBLDocument* doc = [self.otherDBDefaultCollection documentWithID: @"doc1" error: &error]; + CBLBlob* blob2 = [doc blobForKey: @"blob"]; + AssertEqualObjects(blob2.digest, blob.digest); +} + +- (void) testSuspendConflictResolution { + // Prepare conflicts: + NSUInteger numDocs = 1000; + for (NSUInteger i = 0; i < numDocs; i++) { + NSError* error; + NSString* docID = [NSString stringWithFormat: @"doc-%lu", (unsigned long)i]; + CBLMutableDocument *doc1a = [[CBLMutableDocument alloc] initWithID: docID]; + [doc1a setString: self.db.name forKey: @"name"]; + Assert([self.defaultCollection saveDocument: doc1a error: &error]); + + CBLMutableDocument *doc1b = [[CBLMutableDocument alloc] initWithID: docID]; + [doc1b setString: self.otherDB.name forKey: @"name"]; + Assert([self.otherDBDefaultCollection saveDocument: doc1b error: &error]); + } + + NSLock* lock = [[NSLock alloc] init]; + + __block NSUInteger resolvingCount = 0; + XCTestExpectation* resolving = [self allowOverfillExpectationWithDescription: @"Resolver was called"]; + CBLBlockConflictResolver* resolver = [[CBLBlockConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* conflict) { + [lock lock]; + resolvingCount++; + [lock unlock]; + + [resolving fulfill]; + return conflict.remoteDocument; + }]; + + CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection target: _target configBlock:^(CBLCollectionConfiguration* config) { + config.conflictResolver = resolver; + }]; + rConfig.replicatorType = kCBLReplicatorTypePull; + rConfig.continuous = YES; + + CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: rConfig]; + + XCTestExpectation* offline = [self expectationWithDescription: @"Offline"]; + XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; + + id token = [r addChangeListener: ^(CBLReplicatorChange* change) { + NSLog(@">>> %d (%llu/%llu) %@", change.status.activity, change.status.progress.completed, change.status.progress.total, change.status.error); + if (change.status.activity == kCBLReplicatorOffline) { + [offline fulfill]; + } else if (change.status.activity == kCBLReplicatorStopped) { + [stopped fulfill]; + } + }]; + + [r start]; + + // Wait until there is at least one conflict resolver is called. + [self waitForExpectations: @[resolving] timeout: kExpTimeout]; + + // Now suspend. + [r setSuspended: YES]; + + // Wait until no pending conflcit resolver: + NSDate* checkTimeout = [NSDate dateWithTimeIntervalSinceNow: 10.0]; + while (r.pendingConflictCount != 0 && checkTimeout.timeIntervalSinceNow > 0.0) { + if (![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.5]]) { + break; + } + } + + AssertEqual(r.pendingConflictCount, 0); + Assert(resolvingCount > 0); + Assert(resolvingCount < numDocs); + + // Wait until suspended: + [self waitForExpectations: @[offline] timeout: kExpTimeout]; + + // Stop the replicator: + [r stop]; + + // Wait until the replicator is stopped: + [self waitForExpectations: @[stopped] timeout: kExpTimeout]; + + [token remove]; +} + +@end + +#endif // TARGET_OS_IPHONE diff --git a/Objective-C/Tests/ReplicatorTest+Collection.m b/Objective-C/Tests/ReplicatorTest+Collection.m index 52a06fa87..d4366e175 100644 --- a/Objective-C/Tests/ReplicatorTest+Collection.m +++ b/Objective-C/Tests/ReplicatorTest+Collection.m @@ -18,8 +18,6 @@ // #import "ReplicatorTest.h" -#import "CBLReplicator+Internal.h" -#import "CBLCollectionConfiguration+Internal.h" @interface ReplicatorTest_Collection : ReplicatorTest @@ -99,13 +97,14 @@ - (void) testFromCollectionsWithCollectionConfig { id filter1 = ^BOOL(CBLDocument* d, CBLDocumentFlags f) { return YES; }; id filter2 = ^BOOL(CBLDocument* d, CBLDocumentFlags f) { return YES; }; - NSArray* colConfigs = [CBLCollectionConfiguration fromCollections: @[col1a, col1b] config:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - config.pushFilter = filter1; - config.pullFilter = filter2; - config.channels = @[@"channel1", @"channel2", @"channel3"]; - config.documentIDs = @[@"docID1", @"docID2"]; - }]; + NSArray* colConfigs = [CBLCollectionConfiguration fromCollections: @[col1a, col1b]]; + for (CBLCollectionConfiguration* colConfig in colConfigs) { + colConfig.conflictResolver = resolver; + colConfig.pushFilter = filter1; + colConfig.pullFilter = filter2; + colConfig.channels = @[@"channel1", @"channel2", @"channel3"]; + colConfig.documentIDs = @[@"docID1", @"docID2"]; + } CBLReplicatorConfiguration* config = [[CBLReplicatorConfiguration alloc] initWithCollections: colConfigs target: endpoint]; diff --git a/Objective-C/Tests/ReplicatorTest+CustomConflict.m b/Objective-C/Tests/ReplicatorTest+CustomConflict.m index 5c23a31fb..eda306879 100644 --- a/Objective-C/Tests/ReplicatorTest+CustomConflict.m +++ b/Objective-C/Tests/ReplicatorTest+CustomConflict.m @@ -18,9 +18,9 @@ // #import "ReplicatorTest.h" +#ifndef CBL_BINARY_TEST #import "CBLDocument+Internal.h" -#import "CBLErrorMessage.h" -#import "CBLReplicator+Internal.h" +#endif #import "CBLTestCustomLogSink.h" @interface ReplicatorTest_CustomConflict : ReplicatorTest @@ -165,122 +165,10 @@ - (void) testConflictResolverLocalWins { Assert(sequenceBeforePush < [self.otherDBDefaultCollection documentWithID: docId error: nil].sequence); } -- (void) testConflictResolverNullDoc { - TestConflictResolver* resolver; - resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { - return nil; - }]; - - NSError* error; - NSString* docId = @"doc"; - NSDictionary* localData = @{@"key1": @"value1"}; - NSDictionary* remoteData = @{@"key2": @"value2"}; - [self makeConflictFor: docId withLocal: localData withRemote: remoteData]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection - target: _target - configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // Check whether the document is deleted, and returns null. - AssertEqual(self.defaultCollection.count, 0u); - AssertNil([self.defaultCollection documentWithID: docId error: &error]); - - UInt64 sequenceBeforePush = [self.otherDBDefaultCollection documentWithID: docId error: nil].sequence; - - rConfig.replicatorType = kCBLReplicatorTypePush; - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // should be greater, so that it pushed new revision to remote - Assert(sequenceBeforePush < [[CBLDocument alloc] initWithCollection: self.otherDBDefaultCollection - documentID: docId - includeDeleted: YES - error: &error].sequence); -} - /** https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0005-Version-Vector.md Test 4. DefaultConflictResolverDeleteWins -> testConflictResolverDeletedLocalWins + testConflictResolverDeletedRemoteWins */ -- (void) testConflictResolverDeletedLocalWins { - TestConflictResolver* resolver; - resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { - return nil; - }]; - - NSError* error; - NSString* docId = @"doc"; - NSDictionary* remoteData = @{@"key2": @"value2"}; - [self makeConflictFor: docId withLocal: nil withRemote: remoteData]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection - target: _target - configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // Check whether the document gets deleted and return null. - AssertEqual(self.defaultCollection.count, 0u); - AssertNil([self.defaultCollection documentWithID: @"doc" error: &error]); - - UInt64 sequenceBeforePush = [self.otherDBDefaultCollection documentWithID: docId error: nil].sequence; - - rConfig.replicatorType = kCBLReplicatorTypePush; - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // should be greater, so that it pushed new revision to remote - Assert(sequenceBeforePush < [[CBLDocument alloc] initWithCollection: self.otherDBDefaultCollection - documentID: docId - includeDeleted: YES - error: &error].sequence); -} - -- (void) testConflictResolverDeletedRemoteWins { - TestConflictResolver* resolver; - resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { - return nil; - }]; - - NSError* error; - NSString* docId = @"doc"; - NSDictionary* localData = @{@"key1": @"value1"}; - [self makeConflictFor: docId withLocal: localData withRemote: nil]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection - target: _target - configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // Check whether it deletes the document and returns nil. - AssertEqual(self.defaultCollection.count, 0u); - AssertNil([self.defaultCollection documentWithID: @"doc" error: &error]); - - CBLCollection* c = [self.otherDB defaultCollection: nil]; - UInt64 sequenceBeforePush = [[CBLDocument alloc] initWithCollection: c - documentID: docId - includeDeleted: YES - error: &error].sequence; - - rConfig.replicatorType = kCBLReplicatorTypePush; - [self run: rConfig errorCode: 0 errorDomain: nil]; - - // The deleted doc shouldn't be pushed to the remote DB: - AssertEqual(sequenceBeforePush, [[CBLDocument alloc] initWithCollection: c - documentID: docId - includeDeleted: YES - error: &error].sequence); -} - (void) testConflictResolverDeletedBothRev { NSError* error; @@ -760,49 +648,6 @@ - (void) testConflictResolutionDefault { AssertNil([self.defaultCollection documentWithID: @"doc4" error: &error]); } -- (void) testNewDocWithBlob { - NSError* error; - NSString* docID = @"doc"; - NSData* content = [@"I'm a tiger." dataUsingEncoding: NSUTF8StringEncoding]; - CBLBlob* blob = [[CBLBlob alloc] initWithContentType:@"text/plain" data: content]; - - // RESOLVE WITH REMOTE & BLOB data in LOCAL - TestConflictResolver* resolver; - NSDictionary* localData = @{@"key1": @"value1"}; - NSDictionary* remoteData = @{@"key2": @"value2"}; - [self makeConflictFor: docID withLocal: localData withRemote: remoteData]; - resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { - CBLMutableDocument* mDoc = [[CBLMutableDocument alloc] initWithID: con.documentID]; - [mDoc setString: @"newString" forKey: @"newKey"]; - [mDoc setBlob: blob forKey: @"blob"]; - return mDoc; - }]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection - target: _target - configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - - CBLDocument* d = [self.otherDBDefaultCollection documentWithID: docID error: &error]; - Assert((d.c4Doc.revFlags & kRevHasAttachments) == 0); - d = [self.defaultCollection documentWithID: docID error: &error]; - Assert((d.c4Doc.revFlags & kRevHasAttachments) == 0); - - [self run: rConfig errorCode: 0 errorDomain: nil]; - - rConfig.replicatorType = kCBLReplicatorTypePush; - [self run: rConfig errorCode: 0 errorDomain: nil]; - d = [self.otherDBDefaultCollection documentWithID: docID error: &error]; - Assert(d.c4Doc.revFlags & kRevHasAttachments); - AssertEqualObjects([d stringForKey: @"newKey"], @"newString"); - d = [self.defaultCollection documentWithID: docID error: &error]; - Assert(d.c4Doc.revFlags & kRevHasAttachments); - AssertEqualObjects([d stringForKey: @"newKey"], @"newString"); - -} - - (void) testConflictResolverReturningBlob { NSError* error; NSString* docID = @"doc"; @@ -957,89 +802,6 @@ - (void) testNonBlockingConflictResolver { 6. document resolved successfully, with second attempt, 7. once the first CCR tries again, conflict is already been resolved. */ -// CBL-6976: Refactor this test -- (void) dontTestDoubleConflictResolutionOnSameConflicts { - NSError* error; - NSString* docID = @"doc1"; - CBLTestCustomLogSink* logSink = [[CBLTestCustomLogSink alloc] init]; - CBLLogSinks.custom = [[CBLCustomLogSink alloc] initWithLevel: kCBLLogLevelWarning logSink: logSink]; - - XCTestExpectation* expCCR = [self expectationWithDescription:@"wait for conflict resolver"]; - XCTestExpectation* expSTOP = [self expectationWithDescription:@"wait for replicator to stop"]; - XCTestExpectation* expFirstDocResolve = [self expectationWithDescription:@"wait for first conflict to resolve"]; - NSDictionary* localData = @{@"key1": @"value1"}; - NSDictionary* remoteData = @{@"key2": @"value2"}; - [self makeConflictFor: docID withLocal: localData withRemote: remoteData]; - - TestConflictResolver* resolver; - __block int ccrCount = 0; - resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { - int c = ccrCount; - if (ccrCount++ == 0) { - // 2 - [expCCR fulfill]; - [self waitForExpectations: @[expFirstDocResolve] timeout: kExpTimeout]; - } - // 5 - return c == 1 ? con.localDocument /*non-sleeping*/ : con.remoteDocument /*sleeping*/; - }]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection - target: _target - configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - - CBLReplicator* replicator = [[CBLReplicator alloc] initWithConfig: rConfig]; - __weak CBLReplicator* r = replicator; - id changeToken = [replicator addChangeListener:^(CBLReplicatorChange * change) { - __strong CBLReplicator* re = r; - if (change.status.activity == kCBLReplicatorOffline) { - // 4 - [re setSuspended: NO]; - } - if (change.status.activity == kCBLReplicatorStopped) { - [expSTOP fulfill]; - } - }]; - __block int noOfNotificationReceived = 0; - id docReplToken = [replicator addDocumentReplicationListener:^(CBLDocumentReplication * docRepl) { - noOfNotificationReceived++; - if (noOfNotificationReceived == 1) { - // 6 - [expFirstDocResolve fulfill]; - } - AssertEqualObjects(docRepl.documents.firstObject.id, docID); - }]; - - // 1 - [replicator start]; - [self waitForExpectations: @[expCCR] timeout: kExpTimeout]; - - // 3 - // in between the conflict, we wil suspend replicator. - [replicator setSuspended: YES]; - - // Skip exception breakpoint thrown from c4doc_resolve - [self ignoreException:^{ - [self waitForExpectations: @[expSTOP] timeout: kExpTimeout]; - }]; - - AssertEqual(ccrCount, 2u); - AssertEqual(noOfNotificationReceived, 2u); - CBLDocument* doc = [self.defaultCollection documentWithID: docID error: &error]; - AssertEqualObjects([doc toDictionary], localData); - - // 7 - Assert([logSink.lines containsObject: @"Unable to select conflicting revision for doc1, " - "the conflict may have been resolved..."]); - - [changeToken remove]; - [docReplToken remove]; - - CBLLogSinks.custom = nil; -} - (void) testConflictResolverReturningBlobFromDifferentDB { NSString* docID = @"doc"; @@ -1103,7 +865,6 @@ - (void) testConflictResolverReturningBlobFromDifferentDB { }]; AssertNotNil(error); AssertEqual(error.code, CBLErrorUnexpectedError); - AssertEqualObjects(error.userInfo[NSLocalizedDescriptionKey], kCBLErrorMessageBlobDifferentDatabase); [token remove]; } @@ -1146,6 +907,54 @@ - (void) testConflictResolverWhenDocumentIsPurged { [token remove]; } +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testNewDocWithBlob { + NSError* error; + NSString* docID = @"doc"; + NSData* content = [@"I'm a tiger." dataUsingEncoding: NSUTF8StringEncoding]; + CBLBlob* blob = [[CBLBlob alloc] initWithContentType:@"text/plain" data: content]; + + // RESOLVE WITH REMOTE & BLOB data in LOCAL + TestConflictResolver* resolver; + NSDictionary* localData = @{@"key1": @"value1"}; + NSDictionary* remoteData = @{@"key2": @"value2"}; + [self makeConflictFor: docID withLocal: localData withRemote: remoteData]; + resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { + CBLMutableDocument* mDoc = [[CBLMutableDocument alloc] initWithID: con.documentID]; + [mDoc setString: @"newString" forKey: @"newKey"]; + [mDoc setBlob: blob forKey: @"blob"]; + return mDoc; + }]; + + CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection + target: _target + configBlock:^(CBLCollectionConfiguration* config) { + config.conflictResolver = resolver; + }]; + rConfig.replicatorType = kCBLReplicatorTypePull; + + CBLDocument* d = [self.otherDBDefaultCollection documentWithID: docID error: &error]; + Assert((d.c4Doc.revFlags & kRevHasAttachments) == 0); + d = [self.defaultCollection documentWithID: docID error: &error]; + Assert((d.c4Doc.revFlags & kRevHasAttachments) == 0); + + [self run: rConfig errorCode: 0 errorDomain: nil]; + + rConfig.replicatorType = kCBLReplicatorTypePush; + [self run: rConfig errorCode: 0 errorDomain: nil]; + d = [self.otherDBDefaultCollection documentWithID: docID error: &error]; + Assert(d.c4Doc.revFlags & kRevHasAttachments); + AssertEqualObjects([d stringForKey: @"newKey"], @"newString"); + d = [self.defaultCollection documentWithID: docID error: &error]; + Assert(d.c4Doc.revFlags & kRevHasAttachments); + AssertEqualObjects([d stringForKey: @"newKey"], @"newString"); + +} + - (void) testConflictResolverPreservesFlags { NSString* docId = @"doc"; NSData* content = [@"I'm a blob." dataUsingEncoding: NSUTF8StringEncoding]; @@ -1177,4 +986,119 @@ - (void) testConflictResolverPreservesFlags { Assert(localDoc.c4Doc.revFlags & kRevHasAttachments & localRevFlags); } +- (void) testConflictResolverNullDoc { + TestConflictResolver* resolver; + resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { + return nil; + }]; + + NSError* error; + NSString* docId = @"doc"; + NSDictionary* localData = @{@"key1": @"value1"}; + NSDictionary* remoteData = @{@"key2": @"value2"}; + [self makeConflictFor: docId withLocal: localData withRemote: remoteData]; + + CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection + target: _target + configBlock:^(CBLCollectionConfiguration* config) { + config.conflictResolver = resolver; + }]; + rConfig.replicatorType = kCBLReplicatorTypePull; + + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // Check whether the document is deleted, and returns null. + AssertEqual(self.defaultCollection.count, 0u); + AssertNil([self.defaultCollection documentWithID: docId error: &error]); + + UInt64 sequenceBeforePush = [self.otherDBDefaultCollection documentWithID: docId error: nil].sequence; + + rConfig.replicatorType = kCBLReplicatorTypePush; + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // should be greater, so that it pushed new revision to remote + Assert(sequenceBeforePush < [[CBLDocument alloc] initWithCollection: self.otherDBDefaultCollection + documentID: docId + includeDeleted: YES + error: &error].sequence); +} + +- (void) testConflictResolverDeletedLocalWins { + TestConflictResolver* resolver; + resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { + return nil; + }]; + + NSError* error; + NSString* docId = @"doc"; + NSDictionary* remoteData = @{@"key2": @"value2"}; + [self makeConflictFor: docId withLocal: nil withRemote: remoteData]; + + CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection + target: _target + configBlock:^(CBLCollectionConfiguration* config) { + config.conflictResolver = resolver; + }]; + rConfig.replicatorType = kCBLReplicatorTypePull; + + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // Check whether the document gets deleted and return null. + AssertEqual(self.defaultCollection.count, 0u); + AssertNil([self.defaultCollection documentWithID: @"doc" error: &error]); + + UInt64 sequenceBeforePush = [self.otherDBDefaultCollection documentWithID: docId error: nil].sequence; + + rConfig.replicatorType = kCBLReplicatorTypePush; + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // should be greater, so that it pushed new revision to remote + Assert(sequenceBeforePush < [[CBLDocument alloc] initWithCollection: self.otherDBDefaultCollection + documentID: docId + includeDeleted: YES + error: &error].sequence); +} + +- (void) testConflictResolverDeletedRemoteWins { + TestConflictResolver* resolver; + resolver = [[TestConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* con) { + return nil; + }]; + + NSError* error; + NSString* docId = @"doc"; + NSDictionary* localData = @{@"key1": @"value1"}; + [self makeConflictFor: docId withLocal: localData withRemote: nil]; + + CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection + target: _target + configBlock:^(CBLCollectionConfiguration* config) { + config.conflictResolver = resolver; + }]; + rConfig.replicatorType = kCBLReplicatorTypePull; + + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // Check whether it deletes the document and returns nil. + AssertEqual(self.defaultCollection.count, 0u); + AssertNil([self.defaultCollection documentWithID: @"doc" error: &error]); + + CBLCollection* c = [self.otherDB defaultCollection: nil]; + UInt64 sequenceBeforePush = [[CBLDocument alloc] initWithCollection: c + documentID: docId + includeDeleted: YES + error: &error].sequence; + + rConfig.replicatorType = kCBLReplicatorTypePush; + [self run: rConfig errorCode: 0 errorDomain: nil]; + + // The deleted doc shouldn't be pushed to the remote DB: + AssertEqual(sequenceBeforePush, [[CBLDocument alloc] initWithCollection: c + documentID: docId + includeDeleted: YES + error: &error].sequence); +} + +#endif + @end diff --git a/Objective-C/Tests/ReplicatorTest+Main.m b/Objective-C/Tests/ReplicatorTest+Main.m index c84048e4a..420e42ecc 100644 --- a/Objective-C/Tests/ReplicatorTest+Main.m +++ b/Objective-C/Tests/ReplicatorTest+Main.m @@ -18,16 +18,14 @@ // #import "ReplicatorTest.h" -#import "CBLBlockConflictResolver.h" -#import "CBLDatabase+Internal.h" +#ifndef CBL_BINARY_TEST #import "CBLDocumentReplication+Internal.h" -#import "CBLReplicator+Backgrounding.h" -#import "CBLReplicator+Internal.h" #import "CBLWebSocket.h" #import #import #import - +#endif +#import "CBLBlockConflictResolver.h" #define kDummyTarget [[CBLURLEndpoint alloc] initWithURL: [NSURL URLWithString: @"ws://foo.cbl.com/db"]] @@ -145,288 +143,6 @@ - (void) testPullBlob { AssertEqualObjects([savedDoc1 blobForKey:@"blob"], blob); } -#if TARGET_OS_IPHONE - -- (void) testSwitchBackgroundForeground { - - id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; - CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; - - static NSInteger numRounds = 10; - - NSMutableArray* foregroundExps = [NSMutableArray arrayWithCapacity: numRounds + 1]; - NSMutableArray* backgroundExps = [NSMutableArray arrayWithCapacity: numRounds]; - for (NSInteger i = 0; i < numRounds; i++) { - [foregroundExps addObject: [self allowOverfillExpectationWithDescription: @"Foregrounding"]]; - [backgroundExps addObject: [self expectationWithDescription: @"Backgrounding"]]; - } - [foregroundExps addObject: [self allowOverfillExpectationWithDescription: @"Foregrounding"]]; - - __block NSInteger backgroundCount = 0; - __block NSInteger foregroundCount = 0; - - XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; - - id token = [r addChangeListener: ^(CBLReplicatorChange* change) { - AssertNil(change.status.error); - if (change.status.activity == kCBLReplicatorIdle) { - if (foregroundCount <= numRounds) - [foregroundExps[foregroundCount++] fulfill]; - } else if (change.status.activity == kCBLReplicatorOffline) { - [backgroundExps[backgroundCount++] fulfill]; - } else if (change.status.activity == kCBLReplicatorStopped) { - [stopped fulfill]; - } - }]; - - [r start]; - [self waitForExpectations: @[foregroundExps[0]] timeout: kExpTimeout]; - - for (int i = 0; i < numRounds; i++) { - [r appBackgrounding]; - [self waitForExpectations: @[backgroundExps[i]] timeout: kExpTimeout]; - Assert(r.conflictResolutionSuspended); - - [r appForegrounding]; - [self waitForExpectations: @[foregroundExps[i+1]] timeout: kExpTimeout]; - AssertFalse(r.conflictResolutionSuspended); - } - - [r stop]; - [self waitForExpectations: @[stopped] timeout: kExpTimeout]; - - AssertEqual(foregroundCount, numRounds + 1); - AssertEqual(backgroundCount, numRounds); - - [token remove]; - r = nil; -} - -- (void) testSwitchToForegroundImmediately { - id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; - CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; - - XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"idle"]; - XCTestExpectation* foregroundExp = [self allowOverfillExpectationWithDescription: @"Foregrounding"]; - XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; - - __block int idleCount = 0; - id token = [r addChangeListener: ^(CBLReplicatorChange* change) { - AssertNil(change.status.error); - if (change.status.activity == kCBLReplicatorIdle) { - if (idleCount++) - [foregroundExp fulfill]; - else - [idle fulfill]; - } else if (change.status.activity == kCBLReplicatorStopped) { - [stopped fulfill]; - } - }]; - - [r start]; - [self waitForExpectations: @[idle] timeout: kExpTimeout]; - - // Switch to background and immediately comes back to foreground - [r setSuspended: YES]; - [r setSuspended: NO]; - - [self waitForExpectations: @[foregroundExp] timeout: kExpTimeout]; - - [r stop]; - [self waitForExpectations: @[stopped] timeout: kExpTimeout]; - - [token remove]; - r = nil; -} - -- (void) testBackgroundingWhenStopping { - id config = [self configWithTarget: _target type: kCBLReplicatorTypePushAndPull continuous: YES]; - CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: config]; - - __block BOOL foregrounding = NO; - - XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"Idle after starting"]; - XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; - XCTestExpectation* done = [self expectationWithDescription: @"Done"]; - - id token = [r addChangeListener: ^(CBLReplicatorChange* change) { - Assert(!foregrounding); - AssertNil(change.status.error); - Assert(change.status.activity != kCBLReplicatorOffline); - - if (change.status.activity == kCBLReplicatorIdle) { - [idle fulfill]; - } else if (change.status.activity == kCBLReplicatorStopped) { - [stopped fulfill]; - } - }]; - - [r start]; - [self waitForExpectations: @[idle] timeout: kExpTimeout]; - - [r stop]; - - // This shouldn't prevent the replicator to stop: - [r appBackgrounding]; - [self waitForExpectations: @[stopped] timeout: kExpTimeout]; - - // This shouldn't wake up the replicator: - foregrounding = YES; - [r appForegrounding]; - - // Wait for 0.3 seconds to ensure no more changes notified and cause !foregrounding to fail: - id block = [NSBlockOperation blockOperationWithBlock: ^{ [done fulfill]; }]; - [NSTimer scheduledTimerWithTimeInterval: 0.3 - target: block - selector: @selector(main) userInfo: nil repeats: NO]; - [self waitForExpectations: @[done] timeout: kExpTimeout]; - - [token remove]; - r = nil; -} - -- (void) testBackgroundingDuringDataTransfer { - XCTestExpectation* idle = [self allowOverfillExpectationWithDescription: @"idle-and-ready"]; - XCTestExpectation* busy = [self allowOverfillExpectationWithDescription: @"transferring data"]; - XCTestExpectation* offline = [self expectationWithDescription: @"app-in-background"]; - XCTestExpectation* stop = [self allowOverfillExpectationWithDescription: @"finish-transfer"]; - - // setup replicator - CBLReplicatorConfiguration* config = [self configWithTarget: _target type: kCBLReplicatorTypePush - continuous: YES]; - CBLReplicator* replicator = [[CBLReplicator alloc] initWithConfig: config]; - __block int busyCount = 0; - __block int idleCount = 0; - id token = [replicator addChangeListener: ^(CBLReplicatorChange* change) { - if (change.status.activity == kCBLReplicatorIdle) { - if (++idleCount == 1) - [idle fulfill]; - else if (change.status.progress.completed == change.status.progress.total) - [change.replicator stop]; - } else if (change.status.activity == kCBLReplicatorBusy) { - if (++busyCount == 1) - [busy fulfill]; - } else if (change.status.activity == kCBLReplicatorOffline) { - [offline fulfill]; - } else if (change.status.activity == kCBLReplicatorStopped) { - [stop fulfill]; - } - }]; - - // start and wait for idle - AssertEqual(self.otherDBDefaultCollection.count, 0); - [replicator start]; - [self waitForExpectations: @[idle] timeout: kExpTimeout]; - - // replicate a doc with blob, and wait for busy - NSError* error; - CBLMutableDocument* doc1 = [[CBLMutableDocument alloc] initWithID: @"doc1"]; - NSData* data = [self dataFromResource: @"image" ofType: @"jpg"]; - CBLBlob* blob = [[CBLBlob alloc] initWithContentType: @"image/jpg" data: data]; - [doc1 setBlob: blob forKey: @"blob"]; - Assert([self.defaultCollection saveDocument: doc1 error: &error]); - [self waitForExpectations: @[busy] timeout: kExpTimeout]; - - // background during the data transfer! - [replicator setSuspended: YES]; - [self waitForExpectations: @[offline] timeout: kExpTimeout]; - - // forground after 0.2 secs - [NSThread sleepForTimeInterval: 0.2]; - [replicator setSuspended: NO]; - - [self waitForExpectations: @[stop] timeout: kExpTimeout]; - [token remove]; - - // make sure the doc with blob transferred successfully! - AssertEqual(self.otherDBDefaultCollection.count, 1); - CBLDocument* doc = [self.otherDBDefaultCollection documentWithID: @"doc1" error: &error]; - CBLBlob* blob2 = [doc blobForKey: @"blob"]; - AssertEqualObjects(blob2.digest, blob.digest); -} - -- (void) testSuspendConflictResolution { - // Prepare conflicts: - NSUInteger numDocs = 1000; - for (NSUInteger i = 0; i < numDocs; i++) { - NSError* error; - NSString* docID = [NSString stringWithFormat: @"doc-%lu", (unsigned long)i]; - CBLMutableDocument *doc1a = [[CBLMutableDocument alloc] initWithID: docID]; - [doc1a setString: self.db.name forKey: @"name"]; - Assert([self.defaultCollection saveDocument: doc1a error: &error]); - - CBLMutableDocument *doc1b = [[CBLMutableDocument alloc] initWithID: docID]; - [doc1b setString: self.otherDB.name forKey: @"name"]; - Assert([self.otherDBDefaultCollection saveDocument: doc1b error: &error]); - } - - NSLock* lock = [[NSLock alloc] init]; - - __block NSUInteger resolvingCount = 0; - XCTestExpectation* resolving = [self allowOverfillExpectationWithDescription: @"Resolver was called"]; - CBLBlockConflictResolver* resolver = [[CBLBlockConflictResolver alloc] initWithResolver: ^CBLDocument* (CBLConflict* conflict) { - [lock lock]; - resolvingCount++; - [lock unlock]; - - [resolving fulfill]; - return conflict.remoteDocument; - }]; - - CBLReplicatorConfiguration* rConfig = [self configForCollection: self.defaultCollection target: _target configBlock:^(CBLCollectionConfiguration* config) { - config.conflictResolver = resolver; - }]; - rConfig.replicatorType = kCBLReplicatorTypePull; - rConfig.continuous = YES; - - CBLReplicator* r = [[CBLReplicator alloc] initWithConfig: rConfig]; - - XCTestExpectation* offline = [self expectationWithDescription: @"Offline"]; - XCTestExpectation* stopped = [self expectationWithDescription: @"Stopped"]; - - id token = [r addChangeListener: ^(CBLReplicatorChange* change) { - NSLog(@">>> %d (%llu/%llu) %@", change.status.activity, change.status.progress.completed, change.status.progress.total, change.status.error); - if (change.status.activity == kCBLReplicatorOffline) { - [offline fulfill]; - } else if (change.status.activity == kCBLReplicatorStopped) { - [stopped fulfill]; - } - }]; - - [r start]; - - // Wait until there is at least one conflict resolver is called. - [self waitForExpectations: @[resolving] timeout: kExpTimeout]; - - // Now suspend. - [r setSuspended: YES]; - - // Wait until no pending conflcit resolver: - NSDate* checkTimeout = [NSDate dateWithTimeIntervalSinceNow: 10.0]; - while (r.pendingConflictCount != 0 && checkTimeout.timeIntervalSinceNow > 0.0) { - if (![[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.5]]) { - break; - } - } - - AssertEqual(r.pendingConflictCount, 0); - Assert(resolvingCount > 0); - Assert(resolvingCount < numDocs); - - // Wait until suspended: - [self waitForExpectations: @[offline] timeout: kExpTimeout]; - - // Stop the replicator: - [r stop]; - - // Wait until the replicator is stopped: - [self waitForExpectations: @[stopped] timeout: kExpTimeout]; - - [token remove]; -} - -#endif // TARGET_OS_IPHONE - - (void) testStartWithResetCheckpoint { NSError* error; CBLMutableDocument* doc1 = [[CBLMutableDocument alloc] initWithID: @"doc1"]; @@ -768,8 +484,7 @@ - (void) testRemoveDocumentReplicationListener { XCTestExpectation* exp = [self expectationWithDescription: @"Document Replication - Inverted"]; exp.inverted = YES; - - + id config = [self configWithTarget: _target type: kCBLReplicatorTypePush continuous: NO]; [self run: config reset: NO errorCode: 0 errorDomain: nil onReplicatorReady: ^(CBLReplicator* r) { id token = [r addDocumentReplicationListener: ^(CBLDocumentReplication *docReplication) { @@ -1199,83 +914,6 @@ - (void) testRevisionIdInPushPullFilters { Assert([pushDocIds containsObject: @"doc1"]); } -- (void) testWebSocketParseCookie { - NSArray* inputs = @[ - @[@"a1=b1", @[@"a1=b1"]], - @[@"a1=b1;a2=b2", @[@"a1=b1;a2=b2"]], - @[@"a1=b1;expires=b2,3", @[@"a1=b1;expires=b2,3"]], - @[@"a1=b1;a2=b2,a3=b3;a4=b4", @[@"a1=b1;a2=b2", @"a3=b3;a4=b4"]], - @[@"a1=b1;expires=b2,3,a2=b2", @[@"a1=b1;expires=b2,3", @"a2=b2"]], - @[@"a1=b1;expires=b1,2,a3=b3;Expires=b3,4", - @[@"a1=b1;expires=b1,2", @"a3=b3;Expires=b3,4"]], - - // RFC 822, updated by RFC 1123 - @[@"a1=b1;expires=Sun, 06 Nov 1994 08:49:37 GMT;Path=/", - @[@"a1=b1;expires=Sun, 06 Nov 1994 08:49:37 GMT;Path=/"]], - - // RFC 850, obsoleted by RFC 1036 - @[@"a1=b1;expires=Sunday, 06-Nov-94 08:49:37 GMT;Path=/", - @[@"a1=b1;expires=Sunday, 06-Nov-94 08:49:37 GMT;Path=/"]], - - // ANSI C's asctime() format - @[@"a1=b1;expires=Sun Nov 6 08:49:37 1994 ;Path=/", - @[@"a1=b1;expires=Sun Nov 6 08:49:37 1994;Path=/"]], - - // GCLB cookie format => removes in between spaces as well - @[@"GCLB=gclbValue1; path=/; HttpOnly; expires=Tue, 22-Nov-2022 07:21:38 GMT", - @[@"GCLB=gclbValue1;path=/;HttpOnly;expires=Tue, 22-Nov-2022 07:21:38 GMT"]], - ]; - - for (NSArray* input in inputs) { - AssertEqualObjects([CBLWebSocket parseCookies: input[0]], input[1]); - } -} - -- (void) testNetworkInterfaceName { - AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: @"en0" error: nil], @"en0"); - - struct ifaddrs *ifaddrs; - Assert(getifaddrs(&ifaddrs) == 0); - - NSString* networkInterface = nil; - for (struct ifaddrs *ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { - struct sockaddr* addr = ifa->ifa_addr; - if (!addr) - continue; - - int family = ifa->ifa_addr->sa_family; - char host[NI_MAXHOST]; - int s = getnameinfo(ifa->ifa_addr, - (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), - host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); - - if (strcmp(host, "") == 0) - continue; - - AssertEqual(s, 0); - networkInterface = [NSString stringWithUTF8String: ifa->ifa_name]; - if (family == AF_INET) { - AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: [NSString stringWithUTF8String: host] error: nil], networkInterface); - } else if (family == AF_INET6) { - // only checks 'en' series - if (![networkInterface hasPrefix: @"en"]) { - continue; - } - - NSString* hostStr = [NSString stringWithUTF8String: host]; - NSString* localSuffix = [NSString stringWithFormat: @"%%%@", networkInterface]; - NSRange range = [hostStr rangeOfString: localSuffix]; - if (range.length > 0 ) { - NSString* subString = [hostStr substringToIndex: range.location]; - AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: subString error: nil], networkInterface); - } - } - AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: networkInterface error: nil], networkInterface); - } - - freeifaddrs(ifaddrs); -} - #pragma mark - Replicator Config - (void) testReplicationConfigSetterMethods { @@ -1321,7 +959,10 @@ - (void) testReplicationConfigSetterMethods { } - (void) testReplicatorConfigDefaultValues { - CBLReplicatorConfiguration* config = [[CBLReplicatorConfiguration alloc] initWithDefaults]; + CBLURLEndpoint* target = [[CBLURLEndpoint alloc] initWithURL: [NSURL URLWithString: @"ws://localhost:4984/db"]]; + CBLCollectionConfiguration* colConfig = [[CBLCollectionConfiguration alloc] initWithCollection: self.defaultCollection]; + CBLReplicatorConfiguration* config = [[CBLReplicatorConfiguration alloc] initWithCollections: @[colConfig] + target: target]; AssertEqual(config.replicatorType, kCBLDefaultReplicatorType); AssertEqual(config.continuous, kCBLDefaultReplicatorContinuous); @@ -1511,46 +1152,6 @@ - (void) testMaxAttemptWaitTimeOfReplicator { # pragma mark - CBLDocumentReplication -- (void) testCreateDocumentReplicator { - id target = [[CBLURLEndpoint alloc] initWithURL:[NSURL URLWithString:@"ws://foo.couchbase.com/db"]]; - CBLReplicatorConfiguration* config = [self configWithTarget: target - type: kCBLReplicatorTypePush - continuous: YES]; - repl = [[CBLReplicator alloc] initWithConfig: config]; - CBLDocumentReplication* docReplication = [[CBLDocumentReplication alloc] initWithReplicator: repl - isPush: YES - documents: @[]]; - Assert(docReplication.isPush); - AssertEqualObjects(docReplication.documents, @[]); - AssertEqualObjects(docReplication.replicator, repl); - - // Cleanup: - repl = nil; -} - -- (void) testReplicatedDocument { - C4DocumentEnded end; - end.docID = C4STR("docID"); - end.revID = C4STR("revID"); - end.flags = kRevDeleted; - end.error = c4error_make(1, kC4ErrorBusy, C4STR("error")); - end.errorIsTransient = true; - end.collectionSpec = kC4DefaultCollectionSpec; - - CBLReplicatedDocument* replicatedDoc = [[CBLReplicatedDocument alloc] initWithC4DocumentEnded: &end]; - AssertEqualObjects(replicatedDoc.id, @"docID"); - Assert((replicatedDoc.flags & kCBLDocumentFlagsDeleted) == kCBLDocumentFlagsDeleted); - AssertEqual(replicatedDoc.c4Error.code, kC4ErrorBusy); - AssertEqual(replicatedDoc.c4Error.domain, 1); - AssertEqual(replicatedDoc.error.code, kC4ErrorBusy); - AssertEqualObjects(replicatedDoc.collection, kCBLDefaultCollectionName); - AssertEqualObjects(replicatedDoc.scope, kCBLDefaultScopeName); - - [replicatedDoc updateError: nil]; - AssertEqual(replicatedDoc.c4Error.code, 0); - AssertEqual(replicatedDoc.c4Error.domain, 0); - AssertNil(replicatedDoc.error); -} # pragma mark - Change Listener @@ -1611,4 +1212,129 @@ - (void) testGetCorrelationID { Assert(replicator.correlationID.length > 0); } +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testWebSocketParseCookie { + NSArray* inputs = @[ + @[@"a1=b1", @[@"a1=b1"]], + @[@"a1=b1;a2=b2", @[@"a1=b1;a2=b2"]], + @[@"a1=b1;expires=b2,3", @[@"a1=b1;expires=b2,3"]], + @[@"a1=b1;a2=b2,a3=b3;a4=b4", @[@"a1=b1;a2=b2", @"a3=b3;a4=b4"]], + @[@"a1=b1;expires=b2,3,a2=b2", @[@"a1=b1;expires=b2,3", @"a2=b2"]], + @[@"a1=b1;expires=b1,2,a3=b3;Expires=b3,4", + @[@"a1=b1;expires=b1,2", @"a3=b3;Expires=b3,4"]], + + // RFC 822, updated by RFC 1123 + @[@"a1=b1;expires=Sun, 06 Nov 1994 08:49:37 GMT;Path=/", + @[@"a1=b1;expires=Sun, 06 Nov 1994 08:49:37 GMT;Path=/"]], + + // RFC 850, obsoleted by RFC 1036 + @[@"a1=b1;expires=Sunday, 06-Nov-94 08:49:37 GMT;Path=/", + @[@"a1=b1;expires=Sunday, 06-Nov-94 08:49:37 GMT;Path=/"]], + + // ANSI C's asctime() format + @[@"a1=b1;expires=Sun Nov 6 08:49:37 1994 ;Path=/", + @[@"a1=b1;expires=Sun Nov 6 08:49:37 1994;Path=/"]], + + // GCLB cookie format => removes in between spaces as well + @[@"GCLB=gclbValue1; path=/; HttpOnly; expires=Tue, 22-Nov-2022 07:21:38 GMT", + @[@"GCLB=gclbValue1;path=/;HttpOnly;expires=Tue, 22-Nov-2022 07:21:38 GMT"]], + ]; + + for (NSArray* input in inputs) { + AssertEqualObjects([CBLWebSocket parseCookies: input[0]], input[1]); + } +} + +- (void) testNetworkInterfaceName { + AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: @"en0" error: nil], @"en0"); + + struct ifaddrs *ifaddrs; + Assert(getifaddrs(&ifaddrs) == 0); + + NSString* networkInterface = nil; + for (struct ifaddrs *ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + struct sockaddr* addr = ifa->ifa_addr; + if (!addr) + continue; + + int family = ifa->ifa_addr->sa_family; + char host[NI_MAXHOST]; + int s = getnameinfo(ifa->ifa_addr, + (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), + host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST); + + if (strcmp(host, "") == 0) + continue; + + AssertEqual(s, 0); + networkInterface = [NSString stringWithUTF8String: ifa->ifa_name]; + if (family == AF_INET) { + AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: [NSString stringWithUTF8String: host] error: nil], networkInterface); + } else if (family == AF_INET6) { + // only checks 'en' series + if (![networkInterface hasPrefix: @"en"]) { + continue; + } + + NSString* hostStr = [NSString stringWithUTF8String: host]; + NSString* localSuffix = [NSString stringWithFormat: @"%%%@", networkInterface]; + NSRange range = [hostStr rangeOfString: localSuffix]; + if (range.length > 0 ) { + NSString* subString = [hostStr substringToIndex: range.location]; + AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: subString error: nil], networkInterface); + } + } + AssertEqualObjects([CBLWebSocket getNetworkInterfaceName: networkInterface error: nil], networkInterface); + } + + freeifaddrs(ifaddrs); +} + +- (void) testCreateDocumentReplicator { + id target = [[CBLURLEndpoint alloc] initWithURL:[NSURL URLWithString:@"ws://foo.couchbase.com/db"]]; + CBLReplicatorConfiguration* config = [self configWithTarget: target + type: kCBLReplicatorTypePush + continuous: YES]; + repl = [[CBLReplicator alloc] initWithConfig: config]; + CBLDocumentReplication* docReplication = [[CBLDocumentReplication alloc] initWithReplicator: repl + isPush: YES + documents: @[]]; + Assert(docReplication.isPush); + AssertEqualObjects(docReplication.documents, @[]); + AssertEqualObjects(docReplication.replicator, repl); + + // Cleanup: + repl = nil; +} + +- (void) testReplicatedDocument { + C4DocumentEnded end; + end.docID = C4STR("docID"); + end.revID = C4STR("revID"); + end.flags = kRevDeleted; + end.error = c4error_make(1, kC4ErrorBusy, C4STR("error")); + end.errorIsTransient = true; + end.collectionSpec = kC4DefaultCollectionSpec; + + CBLReplicatedDocument* replicatedDoc = [[CBLReplicatedDocument alloc] initWithC4DocumentEnded: &end]; + AssertEqualObjects(replicatedDoc.id, @"docID"); + Assert((replicatedDoc.flags & kCBLDocumentFlagsDeleted) == kCBLDocumentFlagsDeleted); + AssertEqual(replicatedDoc.c4Error.code, kC4ErrorBusy); + AssertEqual(replicatedDoc.c4Error.domain, 1); + AssertEqual(replicatedDoc.error.code, kC4ErrorBusy); + AssertEqualObjects(replicatedDoc.collection, kCBLDefaultCollectionName); + AssertEqualObjects(replicatedDoc.scope, kCBLDefaultScopeName); + + [replicatedDoc updateError: nil]; + AssertEqual(replicatedDoc.c4Error.code, 0); + AssertEqual(replicatedDoc.c4Error.domain, 0); + AssertNil(replicatedDoc.error); +} + +#endif + @end diff --git a/Objective-C/Tests/ReplicatorTest+MessageEndPoint.m b/Objective-C/Tests/ReplicatorTest+MessageEndPoint.m index d620a7563..6dc5660ca 100644 --- a/Objective-C/Tests/ReplicatorTest+MessageEndPoint.m +++ b/Objective-C/Tests/ReplicatorTest+MessageEndPoint.m @@ -7,13 +7,9 @@ // #import "ReplicatorTest.h" -#import "CBLReplicator+Internal.h" -#import "CBLMessageEndpoint.h" #import "CBLMockConnection.h" #import "CBLMockConnectionErrorLogic.h" #import "CBLMockConnectionLifecycleLocation.h" -#import "CBLMessageEndpointListener.h" -#import "CBLErrors.h" @interface MockConnectionFactory : NSObject diff --git a/Objective-C/Tests/ReplicatorTest+PendingDocIds.m b/Objective-C/Tests/ReplicatorTest+PendingDocIds.m index 940d78357..7ec30f94c 100644 --- a/Objective-C/Tests/ReplicatorTest+PendingDocIds.m +++ b/Objective-C/Tests/ReplicatorTest+PendingDocIds.m @@ -18,7 +18,6 @@ // #import "ReplicatorTest.h" -#import "CBLReplicator+Internal.h" #define kDocIdFormat @"doc-%d" #define kActionKey @"action-key" diff --git a/Objective-C/Tests/ReplicatorTest+SG.m b/Objective-C/Tests/ReplicatorTest+SG.m index db273ff4f..3b2636bca 100644 --- a/Objective-C/Tests/ReplicatorTest+SG.m +++ b/Objective-C/Tests/ReplicatorTest+SG.m @@ -19,12 +19,150 @@ #import "ReplicatorTest.h" +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + +#import "CBLJSON.h" +#import "CBLHTTPLogic.h" +#import "CBLURLEndpoint+Internal.h" + @interface ReplicatorTest_SG : ReplicatorTest @end @implementation ReplicatorTest_SG +- (CBLURLEndpoint*) remoteEndpointWithName: (NSString*)dbName secure: (BOOL)secure { + NSString* host = NSProcessInfo.processInfo.environment[@"CBL_TEST_HOST"]; + if (!host) { + Log(@"NOTE: Skipping test: no CBL_TEST_HOST configured in environment"); + return nil; + } + + NSString* portKey = secure ? @"CBL_TEST_PORT_SSL" : @"CBL_TEST_PORT"; + NSInteger port = NSProcessInfo.processInfo.environment[portKey].integerValue; + if (!port) + port = secure ? 4994 : 4984; + + NSURLComponents *comp = [NSURLComponents new]; + comp.scheme = secure ? kCBLURLEndpointTLSScheme : kCBLURLEndpointScheme; + comp.host = host; + comp.port = @(port); + comp.path = [NSString stringWithFormat:@"/%@", dbName]; + NSURL* url = comp.URL; + Assert(url); + + return [[CBLURLEndpoint alloc] initWithURL: url]; +} + ++ (void) initialize { + if (self == [ReplicatorTest_SG class]) { + // You can set environment variables to force use of a proxy: + // CBL_TEST_PROXY_TYPE Proxy type: HTTP, SOCKS, PAC (defaults to HTTP) + // CBL_TEST_PROXY_HOST Proxy hostname + // CBL_TEST_PROXY_PORT Proxy port number + // CBL_TEST_PROXY_USER Username for auth + // CBL_TEST_PROXY_PASS Password for auth + // CBL_TEST_PROXY_PAC_URL URL of PAC file + + NSDictionary* env = NSProcessInfo.processInfo.environment; + NSString* proxyHost = env[@"CBL_TEST_PROXY_HOST"]; + NSString* proxyType = env[@"CBL_TEST_PROXY_TYPE"]; + int proxyPort = [env[@"CBL_TEST_PROXY_PORT"] intValue] ?: 80; + if (proxyHost || proxyType) { + proxyType = [(proxyType ?: @"http") uppercaseString]; + if ([proxyType isEqualToString: @"HTTP"]) + proxyType = (id)kCFProxyTypeHTTP; + else if ([proxyType isEqualToString: @"SOCKS"]) + proxyType = (id)kCFProxyTypeSOCKS; + else if ([proxyType isEqualToString: @"PAC"]) + proxyType = (id)kCFProxyTypeAutoConfigurationURL; + NSMutableDictionary* proxy = [@{(id)kCFProxyTypeKey: proxyType} mutableCopy]; + proxy[(id)kCFProxyHostNameKey] = proxyHost; + proxy[(id)kCFProxyPortNumberKey] = @(proxyPort); + if (proxyType == (id)kCFProxyTypeAutoConfigurationURL) { + NSURL* pacURL = [NSURL URLWithString: env[@"CBL_TEST_PROXY_PAC_URL"]]; + proxy[(id)kCFProxyAutoConfigurationURLKey] = pacURL; + Log(@"Using PAC proxy URL %@", pacURL); + } else { + Log(@"Using %@ proxy server %@:%d", proxyType, proxyHost, proxyPort); + } + proxy[(id)kCFProxyUsernameKey] = env[@"CBL_TEST_PROXY_USER"]; + proxy[(id)kCFProxyPasswordKey] = env[@"CBL_TEST_PROXY_PASS"]; + [CBLHTTPLogic setOverrideProxySettings: proxy]; + } + } +} + +- (void) eraseRemoteEndpoint: (CBLURLEndpoint*)endpoint { + Assert([endpoint.url.path isEqualToString: @"/scratch"], @"Only scratch db should be erased"); + [self sendRequestToEndpoint: endpoint method: @"POST" path: @"_flush" body: nil]; + Log(@"Erased remote database %@", endpoint.url); +} + +- (id) sendRequestToEndpoint: (CBLURLEndpoint*)endpoint + method: (NSString*)method + path: (nullable NSString*)path + body: (nullable id)body +{ + NSURL* endpointURL = endpoint.url; + NSURLComponents *comp = [NSURLComponents new]; + comp.scheme = [endpointURL.scheme isEqualToString: kCBLURLEndpointTLSScheme] ? @"https" : @"http"; + comp.host = endpointURL.host; + comp.port = @([endpointURL.port intValue] + 1); // assuming admin port is at usual offse + path = path ? ([path hasPrefix: @"/"] ? path : [@"/" stringByAppendingString: path]) : @""; + comp.path = [NSString stringWithFormat: @"%@%@", endpointURL.path, path]; + NSURL* url = comp.URL; + Assert(url); + + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url]; + request.HTTPMethod = method; + if (body) { + if ([body isKindOfClass: [NSData class]]) { + request.HTTPBody = body; + } else { + NSError* err = nil; + request.HTTPBody = [CBLJSON dataWithJSONObject: body options:0 error: &err]; + AssertNil(err); + } + } + + __block NSError* error = nil; + __block NSInteger status = 0; + __block NSData* data = nil; + XCTestExpectation* x = [self expectationWithDescription: @"Complete Request"]; + NSURLSessionDataTask* task = [[NSURLSession sharedSession] dataTaskWithRequest: request + completionHandler: + ^(NSData *d, NSURLResponse *r, NSError *e) + { + error = e; + data = d; + status = ((NSHTTPURLResponse*)r).statusCode; + [x fulfill]; + }]; + [task resume]; + [self waitForExpectations: @[x] timeout: kExpTimeout]; + + if (error != nil || status >= 300) { + XCTFail(@"Failed to send request; URL=<%@>, Method=<%@>, Status=%ld, Error=%@", + url, method, (long)status, error); + return nil; + } else { + Log(@"Send request succeeded; URL=<%@>, Method=<%@>, Status=%ld", + url, method, (long)status); + id result = nil; + if (data && data.length > 0) { + result = [CBLJSON JSONObjectWithData: data options: 0 error: &error]; + Assert(result, @"Couldn't parse JSON response: %@", error); + } + return result; + } +} + + // TODO: Remove https://issues.couchbase.com/browse/CBL-3206 #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" diff --git a/Objective-C/Tests/ReplicatorTest.h b/Objective-C/Tests/ReplicatorTest.h index df67af9ef..5020bfd10 100644 --- a/Objective-C/Tests/ReplicatorTest.h +++ b/Objective-C/Tests/ReplicatorTest.h @@ -20,8 +20,10 @@ #import "CBLTestCase.h" #ifdef COUCHBASE_ENTERPRISE +#ifndef CBL_BINARY_TEST #import "CBLReplicatorConfiguration+ServerCert.h" #endif +#endif NS_ASSUME_NONNULL_BEGIN @@ -42,21 +44,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Endpoint -/** Returns an endpoint for a Sync Gateway test database, or nil if SG tests are not enabled. - To enable these tests, set the hostname of the server in the environment variable - "CBL_TEST_HOST". - The port number defaults to 4984, or 4994 for SSL. To override these, set the environment - variables "CBL_TEST_PORT" and/or "CBL_TEST_PORT_SSL". - Note: On iOS, all endpoints will be SSL regardless of the `secure` flag. - */ -- (nullable CBLURLEndpoint*) remoteEndpointWithName: (NSString*)dbName secure: (BOOL)secure; - -- (void) eraseRemoteEndpoint: (CBLURLEndpoint*)endpoint; - -- (nullable id) sendRequestToEndpoint: (CBLURLEndpoint*)endpoint - method: (NSString*)method - path: (nullable NSString*)path - body: (nullable id)body; #pragma mark - Certifciate diff --git a/Objective-C/Tests/ReplicatorTest.m b/Objective-C/Tests/ReplicatorTest.m index 15daaca93..4e8a83e5f 100644 --- a/Objective-C/Tests/ReplicatorTest.m +++ b/Objective-C/Tests/ReplicatorTest.m @@ -18,14 +18,9 @@ // #import "ReplicatorTest.h" -#import "CBLJSON.h" -#import "CBLHTTPLogic.h" #import "CollectionUtils.h" -#import "CBLURLEndpoint+Internal.h" -#import "CBLReplicator+Internal.h" #ifdef COUCHBASE_ENTERPRISE -#import "CBLReplicatorConfiguration+ServerCert.h" #endif @implementation ReplicatorTest { @@ -39,45 +34,6 @@ @implementation ReplicatorTest { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" -+ (void) initialize { - if (self == [ReplicatorTest class]) { - // You can set environment variables to force use of a proxy: - // CBL_TEST_PROXY_TYPE Proxy type: HTTP, SOCKS, PAC (defaults to HTTP) - // CBL_TEST_PROXY_HOST Proxy hostname - // CBL_TEST_PROXY_PORT Proxy port number - // CBL_TEST_PROXY_USER Username for auth - // CBL_TEST_PROXY_PASS Password for auth - // CBL_TEST_PROXY_PAC_URL URL of PAC file - - NSDictionary* env = NSProcessInfo.processInfo.environment; - NSString* proxyHost = env[@"CBL_TEST_PROXY_HOST"]; - NSString* proxyType = env[@"CBL_TEST_PROXY_TYPE"]; - int proxyPort = [env[@"CBL_TEST_PROXY_PORT"] intValue] ?: 80; - if (proxyHost || proxyType) { - proxyType = [(proxyType ?: @"http") uppercaseString]; - if ([proxyType isEqualToString: @"HTTP"]) - proxyType = (id)kCFProxyTypeHTTP; - else if ([proxyType isEqualToString: @"SOCKS"]) - proxyType = (id)kCFProxyTypeSOCKS; - else if ([proxyType isEqualToString: @"PAC"]) - proxyType = (id)kCFProxyTypeAutoConfigurationURL; - NSMutableDictionary* proxy = [@{(id)kCFProxyTypeKey: proxyType} mutableCopy]; - proxy[(id)kCFProxyHostNameKey] = proxyHost; - proxy[(id)kCFProxyPortNumberKey] = @(proxyPort); - if (proxyType == (id)kCFProxyTypeAutoConfigurationURL) { - NSURL* pacURL = [NSURL URLWithString: env[@"CBL_TEST_PROXY_PAC_URL"]]; - proxy[(id)kCFProxyAutoConfigurationURLKey] = pacURL; - Log(@"Using PAC proxy URL %@", pacURL); - } else { - Log(@"Using %@ proxy server %@:%d", proxyType, proxyHost, proxyPort); - } - proxy[(id)kCFProxyUsernameKey] = env[@"CBL_TEST_PROXY_USER"]; - proxy[(id)kCFProxyPasswordKey] = env[@"CBL_TEST_PROXY_PASS"]; - [CBLHTTPLogic setOverrideProxySettings: proxy]; - } - } -} - - (void) setUp { [super setUp]; @@ -93,94 +49,6 @@ - (void) tearDown { #pragma mark - Endpoint -- (CBLURLEndpoint*) remoteEndpointWithName: (NSString*)dbName secure: (BOOL)secure { - NSString* host = NSProcessInfo.processInfo.environment[@"CBL_TEST_HOST"]; - if (!host) { - Log(@"NOTE: Skipping test: no CBL_TEST_HOST configured in environment"); - return nil; - } - - NSString* portKey = secure ? @"CBL_TEST_PORT_SSL" : @"CBL_TEST_PORT"; - NSInteger port = NSProcessInfo.processInfo.environment[portKey].integerValue; - if (!port) - port = secure ? 4994 : 4984; - - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = secure ? kCBLURLEndpointTLSScheme : kCBLURLEndpointScheme; - comp.host = host; - comp.port = @(port); - comp.path = [NSString stringWithFormat:@"/%@", dbName]; - NSURL* url = comp.URL; - Assert(url); - - return [[CBLURLEndpoint alloc] initWithURL: url]; -} - -- (void) eraseRemoteEndpoint: (CBLURLEndpoint*)endpoint { - Assert([endpoint.url.path isEqualToString: @"/scratch"], @"Only scratch db should be erased"); - [self sendRequestToEndpoint: endpoint method: @"POST" path: @"_flush" body: nil]; - Log(@"Erased remote database %@", endpoint.url); -} - -- (id) sendRequestToEndpoint: (CBLURLEndpoint*)endpoint - method: (NSString*)method - path: (nullable NSString*)path - body: (nullable id)body -{ - NSURL* endpointURL = endpoint.url; - NSURLComponents *comp = [NSURLComponents new]; - comp.scheme = [endpointURL.scheme isEqualToString: kCBLURLEndpointTLSScheme] ? @"https" : @"http"; - comp.host = endpointURL.host; - comp.port = @([endpointURL.port intValue] + 1); // assuming admin port is at usual offse - path = path ? ([path hasPrefix: @"/"] ? path : [@"/" stringByAppendingString: path]) : @""; - comp.path = [NSString stringWithFormat: @"%@%@", endpointURL.path, path]; - NSURL* url = comp.URL; - Assert(url); - - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: url]; - request.HTTPMethod = method; - if (body) { - if ([body isKindOfClass: [NSData class]]) { - request.HTTPBody = body; - } else { - NSError* err = nil; - request.HTTPBody = [CBLJSON dataWithJSONObject: body options:0 error: &err]; - AssertNil(err); - } - } - - __block NSError* error = nil; - __block NSInteger status = 0; - __block NSData* data = nil; - XCTestExpectation* x = [self expectationWithDescription: @"Complete Request"]; - NSURLSessionDataTask* task = [[NSURLSession sharedSession] dataTaskWithRequest: request - completionHandler: - ^(NSData *d, NSURLResponse *r, NSError *e) - { - error = e; - data = d; - status = ((NSHTTPURLResponse*)r).statusCode; - [x fulfill]; - }]; - [task resume]; - [self waitForExpectations: @[x] timeout: kExpTimeout]; - - if (error != nil || status >= 300) { - XCTFail(@"Failed to send request; URL=<%@>, Method=<%@>, Status=%ld, Error=%@", - url, method, (long)status, error); - return nil; - } else { - Log(@"Send request succeeded; URL=<%@>, Method=<%@>, Status=%ld", - url, method, (long)status); - id result = nil; - if (data && data.length > 0) { - result = [CBLJSON JSONObjectWithData: data options: 0 error: &error]; - Assert(result, @"Couldn't parse JSON response: %@", error); - } - return result; - } -} - #pragma mark - Certificate - (SecCertificateRef) defaultServerCert { @@ -256,9 +124,6 @@ - (CBLReplicatorConfiguration*) configWithTarget: (id)target c.pinnedServerCertificate = self.defaultServerCert; } - if (continuous) - c.checkpointInterval = 1.0; // For testing only - return c; } diff --git a/Objective-C/Tests/TLSIdentityTest.m b/Objective-C/Tests/TLSIdentityTest.m index 3a7d7e59d..cdd7b7c81 100644 --- a/Objective-C/Tests/TLSIdentityTest.m +++ b/Objective-C/Tests/TLSIdentityTest.m @@ -18,8 +18,10 @@ // #import "CBLTestCase.h" +#ifndef CBL_BINARY_TEST #import "CBLTLSIdentity+Internal.h" #import "CBLTrustCheck.h" +#endif @interface TLSIdentityTest : CBLTestCase @@ -560,6 +562,13 @@ - (void) testCertificateExpiration { #endif } +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +// Note: a public rewrite (import the issuer via importIdentityWithData and verify with +// SecTrust) is blocked by CBL-7005 (importIdentityWithData broken on newer macOS). +#ifndef CBL_BINARY_TEST + - (void) testCreateIdentitySignedWithIssuer { XCTSkipUnless(self.keyChainAccessAllowed); @@ -669,4 +678,6 @@ - (void) testCreateIdentitySignedWithImportedIssuer { AssertNil(error); } +#endif + @end diff --git a/Objective-C/Tests/TrustCheckTest.m b/Objective-C/Tests/TrustCheckTest.m index be40902fb..0716cc5ff 100644 --- a/Objective-C/Tests/TrustCheckTest.m +++ b/Objective-C/Tests/TrustCheckTest.m @@ -18,6 +18,13 @@ // #import "CBLTestCase.h" + +// This test file uses internal APIs and is for the internal test targets only; +// it cannot be part of the binary test targets (CBL_*_Binary_Tests). +#ifdef CBL_BINARY_TEST +#error This test file uses internal APIs and cannot run against a binary framework. +#endif + #import "CBLTrustCheck.h" @interface TrustCheckTest : CBLTestCase diff --git a/Objective-C/Tests/URLEndpointListenerTest+Main.m b/Objective-C/Tests/URLEndpointListenerTest+Main.m index af3898b4f..b6076da31 100644 --- a/Objective-C/Tests/URLEndpointListenerTest+Main.m +++ b/Objective-C/Tests/URLEndpointListenerTest+Main.m @@ -18,6 +18,9 @@ // #import "URLEndpointListenerTest.h" +#ifndef CBL_BINARY_TEST +#import "CBLURLEndpointListener+Internal.h" +#endif #import "CollectionUtils.h" @interface URLEndpointListenerTest_Main : URLEndpointListenerTest @@ -49,8 +52,7 @@ - (void) validateMultipleReplicationsTo: (Listener*)listener replType: (CBLRepli CBLDatabase* db2 = [self openDBNamed: @"db2" error: &error]; CBLCollection* db2Col = [db2 defaultCollection: &error]; AssertNil(error); - - + NSData* content = [@"i am a blob" dataUsingEncoding: NSUTF8StringEncoding]; @@ -435,7 +437,7 @@ - (void) testTLSListenerAnonymousIdentity { serverCert: (__bridge SecCertificateRef)identity.certs[0] errorCode: CBLErrorTLSCertUnknownRoot errorDomain: CBLErrorDomain]; - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; // No pinned cert [self runWithTarget: listener.localEndpoint @@ -484,7 +486,7 @@ - (void) testTLSListenerUserIdentity { serverCert: (__bridge SecCertificateRef)identity.certs[0] errorCode: CBLErrorTLSCertUnknownRoot errorDomain: CBLErrorDomain]; - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; // No pinned cert [self runWithTarget: listener.localEndpoint @@ -536,7 +538,7 @@ - (void) testNonTLSNullListenerAuthenticator { errorDomain: nil]; // cleanup client cert authenticator identity - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; [self stopListener: listener]; } @@ -588,7 +590,7 @@ - (void) testNonTLSPasswordListenerAuthenticator { errorDomain: CBLErrorDomain]; // cleanup client cert authenticator identity - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; // Replicator - Success: [self runWithTarget: listener.localEndpoint @@ -653,7 +655,7 @@ - (void) testTLSPasswordListenerAuthenticator { errorDomain: CBLErrorDomain]; // cleanup client cert authenticator identity - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; // Replicator - Success: [self runWithTarget: listener.localEndpoint @@ -698,7 +700,7 @@ - (void) testClientCertAuthWithCallback { errorDomain: nil]; // Cleanup client cert authenticator identity - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; [self stopListener: listener]; } @@ -727,7 +729,7 @@ - (void) testClientCertAuthWithCallbackError { errorDomain: CBLErrorDomain]; // Cleanup: - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; [self stopListener: listener]; } @@ -808,7 +810,7 @@ - (void) testClientCertAuthRootCertsError { }]; // Cleanup: - [self cleanupTLSIdentity: NO]; + [self cleanUpTLSIdentityForServer: NO]; [self stopListener: listener]; } @@ -885,22 +887,6 @@ - (void) testUnavailableNetworkInterface { }]; } -- (void) testNetworkInterfaceName { - if (!self.keyChainAccessAllowed) return; - - NSArray* interfaces = [Listener allInterfaceNames]; - for (NSString* i in interfaces) { - Config* config = [[Config alloc] initWithCollections: @[self.otherDBDefaultCollection]]; - config.networkInterface = i; - - [self listen: config]; - - /// make sure, connection is successful and no error thrown! - - [self stopListen]; - } -} - - (void) testMultipleListenersOnSameDatabase { if (!self.keyChainAccessAllowed) return; @@ -1007,10 +993,6 @@ - (void) testCloseWithActiveListener { AssertEqual(_listener.port, 0); AssertEqual(_listener.urls.count, 0); - // Cleanup: - if (identity) { - [self deleteFromKeyChain: identity]; - } } - (void) testReplicatorServerCertificate { @@ -1478,4 +1460,79 @@ - (void) testDeleteWithActiveReplicatorAndURLEndpointListeners { [self validateActiveReplicatorAndURLEndpointListeners: YES]; } +- (void) testCleanUpAnonymousIdentities { + if (!self.keyChainAccessAllowed) return; + + // Starting a TLS listener without an identity creates an anonymous identity: + Config* config = [[Config alloc] initWithCollections: @[self.otherDBDefaultCollection]]; + [self listen: config]; + AssertNotNil(_listener.tlsIdentity); + [self stopListen]; + + [self cleanUpAnonymousIdentities]; + + // No anonymous identities nor certificates should be left in the keychain: + AssertEqual([self anonymousIdentityCount], 0); + AssertEqual([self anonymousCertificateCount], 0); +} + +- (NSUInteger) anonymousIdentityCount { + return [self keychainItemCount: (id)kSecClassIdentity]; +} + +- (NSUInteger) anonymousCertificateCount { + return [self keychainItemCount: (id)kSecClassCertificate]; +} + +- (NSUInteger) keychainItemCount: (id)itemClass { + NSDictionary* query = @{(id)kSecClass: itemClass, + (id)kSecMatchLimit: (id)kSecMatchLimitAll, + (id)kSecReturnRef: @YES}; + CFTypeRef result = NULL; + OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &result); + if (status == errSecItemNotFound) + return 0; + Assert(status == errSecSuccess, @"Cannot query keychain items (OSStatus = %d)", (int)status); + + NSUInteger count = 0; + NSArray* items = CFBridgingRelease(result); + for (id item in items) { + SecCertificateRef certRef = NULL; + if (itemClass == (id)kSecClassIdentity) { + if (SecIdentityCopyCertificate((__bridge SecIdentityRef)item, &certRef) != errSecSuccess || !certRef) + continue; + } else { + certRef = (SecCertificateRef)CFRetain((__bridge SecCertificateRef)item); + } + NSString* name = CFBridgingRelease(SecCertificateCopySubjectSummary(certRef)); + CFRelease(certRef); + if ([name isEqualToString: kCBLAnonymousIdentityCommonName]) + count++; + } + return count; +} + +#pragma mark - Internal + +// White-box tests that verify internal state; excluded from the binary tests. +#ifndef CBL_BINARY_TEST + +- (void) testNetworkInterfaceName { + if (!self.keyChainAccessAllowed) return; + + NSArray* interfaces = [Listener allInterfaceNames]; + for (NSString* i in interfaces) { + Config* config = [[Config alloc] initWithCollections: @[self.otherDBDefaultCollection]]; + config.networkInterface = i; + + [self listen: config]; + + /// make sure, connection is successful and no error thrown! + + [self stopListen]; + } +} + +#endif + @end diff --git a/Objective-C/Tests/URLEndpointListenerTest.h b/Objective-C/Tests/URLEndpointListenerTest.h index 05bf10bd2..964f48d23 100644 --- a/Objective-C/Tests/URLEndpointListenerTest.h +++ b/Objective-C/Tests/URLEndpointListenerTest.h @@ -18,12 +18,14 @@ // #import "ReplicatorTest.h" -#import "CBLURLEndpointListenerConfiguration.h" -#import "CBLURLEndpointListener+Internal.h" #define kWsPort 4084 #define kWssPort 4085 +// The common name used by the anonymous identities that the listener creates when +// started without an identity. Mirrors kCBLAnonymousIdentityCommonName defined in +// Sources/Objective-C/Listener/CBLURLEndpointListener.mm (couchbase-lite-ios-ee). +#define kCBLAnonymousIdentityCommonName @"CBLAnonymousCertificate" #define kServerCertLabel @"CBL-Server-Cert" #define kClientCertLabel @"CBL-Client-Cert" @@ -49,8 +51,10 @@ NS_ASSUME_NONNULL_BEGIN // TLS Identity management - (nullable CBLTLSIdentity*) tlsIdentity: (BOOL)isServer; -- (void) cleanupTLSIdentity: (BOOL)isServer; -- (void) deleteFromKeyChain: (CBLTLSIdentity*)identity; +- (void) cleanUpTLSIdentityForServer: (BOOL)isServer; + +/** Deletes all anonymous identities and their certificates from the keychain. */ +- (void) cleanUpAnonymousIdentities; // replicator methods - (CBLReplicator*) replicator: (CBLDatabase*)db diff --git a/Objective-C/Tests/URLEndpointListenerTest.m b/Objective-C/Tests/URLEndpointListenerTest.m index 4a00236f7..e18f47633 100644 --- a/Objective-C/Tests/URLEndpointListenerTest.m +++ b/Objective-C/Tests/URLEndpointListenerTest.m @@ -18,10 +18,7 @@ // #import "ReplicatorTest.h" -#import "CBLTLSIdentity+Internal.h" -#import "CBLURLEndpointListener+Internal.h" -#import "CBLURLEndpointListenerConfiguration+Internal.h" -#import "CBLMessageEndpointListenerConfiguration+Internal.h" +#import #import "CollectionUtils.h" #import "URLEndpointListenerTest.h" @@ -33,7 +30,7 @@ - (NSURL*) localURL { comps.scheme = self.config.disableTLS ? @"ws" : @"wss"; comps.host = @"localhost"; comps.port = @(self.port); - comps.path = $sprintf(@"/%@",self.config.database.name); + comps.path = $sprintf(@"/%@",((CBLCollection*)self.config.collections.firstObject).database.name); return comps.URL; } @@ -48,12 +45,14 @@ @implementation URLEndpointListenerTest - (void) setUp { [super setUp]; - [self cleanUpIdentities]; + [self cleanUpAnonymousIdentities]; } - (void) tearDown { [self stopListen]; - [self cleanUpIdentities]; + [self cleanUpAnonymousIdentities]; + [self cleanUpTLSIdentityForServer: YES]; + [self cleanUpTLSIdentityForServer: NO]; [super tearDown]; } @@ -117,19 +116,7 @@ - (void) stopListen { } - (void) stopListener: (CBLURLEndpointListener*)listener { - CBLTLSIdentity* identity = listener.tlsIdentity; [listener stop]; - if (identity && self.keyChainAccessAllowed) { - [self deleteFromKeyChain: identity]; - } -} - -- (void) deleteFromKeyChain: (CBLTLSIdentity*)identity { - [self ignoreException:^{ - NSError* error; - Assert([identity deleteFromKeyChainWithError: &error], - @"Couldn't delete identity: %@", error); - }]; } - (CBLReplicator*) replicator: (CBLDatabase*)db @@ -173,7 +160,7 @@ - (CBLTLSIdentity*) tlsIdentity: (BOOL)isServer { if (!self.keyChainAccessAllowed) return nil; // Cleanup: - [self cleanupTLSIdentity: isServer]; + [self cleanUpTLSIdentityForServer: isServer]; // Create server/client identity: NSError* err; @@ -190,24 +177,76 @@ - (CBLTLSIdentity*) tlsIdentity: (BOOL)isServer { return identity; } -- (void) cleanupTLSIdentity: (BOOL)isServer { +- (void) cleanUpTLSIdentityForServer: (BOOL)isServer { if (!self.keyChainAccessAllowed) return; - NSError* err; + // Delete directly from the keychain by label so that partial identities + // (e.g. a leftover certificate without its key) get cleaned up as well: NSString* label = isServer ? kServerCertLabel : kClientCertLabel; - Assert([CBLTLSIdentity deleteIdentityWithLabel: label error: &err]); + for (id itemClass in @[(id)kSecClassIdentity, (id)kSecClassCertificate]) { + NSDictionary* query = @{(id)kSecClass: itemClass, + (id)kSecAttrLabel: label}; + OSStatus status = SecItemDelete((CFDictionaryRef)query); + Assert(status == errSecSuccess || status == errSecItemNotFound || status == errSecInvalidItemRef || + status == errSecWrPerm /* items in a keychain that tests cannot modify (e.g. Local Items) */, + @"Couldn't delete keychain items with label %@ (OSStatus = %d)", label, (int)status); + } } - (void) releaseCF: (CFTypeRef)ref { if (ref != NULL) CFRelease(ref); } -- (void) cleanUpIdentities { +- (void) cleanUpAnonymousIdentities { if (self.keyChainAccessAllowed) { [self ignoreException: ^{ - NSError* error; - Assert([CBLURLEndpointListener deleteAnonymousIdentitiesWithError: &error], - @"Cannot delete anonymous identity: %@", error); + NSDictionary* query = @{(id)kSecClass: (id)kSecClassIdentity, + (id)kSecMatchLimit: (id)kSecMatchLimitAll, + (id)kSecReturnRef: @YES}; + CFTypeRef result = NULL; + OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &result); + if (status == errSecItemNotFound) + return; + Assert(status == errSecSuccess, @"Cannot query identities (OSStatus = %d)", (int)status); + NSArray* identities = CFBridgingRelease(result); + for (id identityObj in identities) { + SecIdentityRef identityRef = (__bridge SecIdentityRef)identityObj; + SecCertificateRef certRef = NULL; + if (SecIdentityCopyCertificate(identityRef, &certRef) != errSecSuccess || !certRef) + continue; + NSString* name = CFBridgingRelease(SecCertificateCopySubjectSummary(certRef)); + CFRelease(certRef); + if ([name isEqualToString: kCBLAnonymousIdentityCommonName]) { + NSDictionary* del = @{(id)kSecClass: (id)kSecClassIdentity, + (id)kSecValueRef: identityObj}; + status = SecItemDelete((CFDictionaryRef)del); + Assert(status == errSecSuccess || status == errSecItemNotFound || status == errSecInvalidItemRef, + @"Cannot delete anonymous identity (OSStatus = %d)", (int)status); + } + } + + // Deleting an identity doesn't remove its certificate; sweep the anonymous + // certificates (including any orphaned by previously interrupted test runs): + query = @{(id)kSecClass: (id)kSecClassCertificate, + (id)kSecMatchLimit: (id)kSecMatchLimitAll, + (id)kSecReturnRef: @YES}; + result = NULL; + status = SecItemCopyMatching((CFDictionaryRef)query, &result); + if (status == errSecItemNotFound) + return; + Assert(status == errSecSuccess, @"Cannot query certificates (OSStatus = %d)", (int)status); + NSArray* certs = CFBridgingRelease(result); + for (id certObj in certs) { + NSString* name = CFBridgingRelease( + SecCertificateCopySubjectSummary((__bridge SecCertificateRef)certObj)); + if ([name isEqualToString: kCBLAnonymousIdentityCommonName]) { + NSDictionary* del = @{(id)kSecClass: (id)kSecClassCertificate, + (id)kSecValueRef: certObj}; + status = SecItemDelete((CFDictionaryRef)del); + Assert(status == errSecSuccess || status == errSecItemNotFound || status == errSecInvalidItemRef, + @"Cannot delete anonymous certificate (OSStatus = %d)", (int)status); + } + } }]; } } diff --git a/Objective-C/Tests/UnnestArrayIndexTest.m b/Objective-C/Tests/UnnestArrayIndexTest.m index b7f6048cf..d33d52d51 100644 --- a/Objective-C/Tests/UnnestArrayIndexTest.m +++ b/Objective-C/Tests/UnnestArrayIndexTest.m @@ -17,8 +17,9 @@ // #import "CBLTestCase.h" -#import "CBLArrayIndexConfiguration.h" +#ifndef CBL_BINARY_TEST #import "CBLCollection+Internal.h" +#endif @interface UnnestArrayIndexTest : CBLTestCase @@ -44,11 +45,11 @@ @implementation UnnestArrayIndexTest - (void) testArrayIndexConfigInvalidExpressions { [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLArrayIndexConfiguration alloc] initWithPath:@"contacts" expressions: @[]]; + (void) [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: @[]]; }]; [self expectException: NSInvalidArgumentException in:^{ - (void) [[CBLArrayIndexConfiguration alloc] initWithPath:@"contacts" expressions: @[@""]]; + (void) [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: @[@""]]; }]; } @@ -73,9 +74,16 @@ - (void) testCreateArrayIndexWithPath { CBLArrayIndexConfiguration* config = [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: nil]; [profiles createIndexWithName: @"contacts" config: config error: &err]; + + // Step 4: Check that the index named "contacts" exists: + AssertEqualObjects([profiles indexes: &err], @[@"contacts"]); + + // Step 5: Check the index's configured expressions using the internal API: +#ifndef CBL_BINARY_TEST NSArray* indexes = [profiles indexesInfo: nil]; AssertEqual(indexes.count, 1u); AssertEqualObjects(indexes[0][@"expr"], @""); +#endif } /** @@ -96,12 +104,20 @@ - (void) testCreateArrayIndexWithPathAndExpressions { CBLCollection* profiles = [self.db createCollectionWithName: @"profiles" scope: nil error: &err]; [self loadJSONResource: @"profiles_100" toCollection: profiles]; - CBLArrayIndexConfiguration* config = [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" expressions: @[@"address.city", @"address.state"]]; + CBLArrayIndexConfiguration* config = + [[CBLArrayIndexConfiguration alloc] initWithPath: @"contacts" + expressions: @[@"address.city", @"address.state"]]; [profiles createIndexWithName: @"contacts" config: config error: &err]; + // Check that the index named "contacts" exists: + AssertEqualObjects([profiles indexes: &err], @[@"contacts"]); + + // Check the index's configured expressions using the internal API: +#ifndef CBL_BINARY_TEST NSArray* indexes = [profiles indexesInfo: nil]; AssertEqual(indexes.count, 1u); AssertEqualObjects(indexes[0][@"expr"], @"address.city,address.state"); +#endif } @end diff --git a/Objective-C/Tests/Util/CBLBlockConflictResolver.h b/Objective-C/Tests/Util/CBLBlockConflictResolver.h index cc3ab24f0..298a41b63 100644 --- a/Objective-C/Tests/Util/CBLBlockConflictResolver.h +++ b/Objective-C/Tests/Util/CBLBlockConflictResolver.h @@ -17,7 +17,7 @@ // #import -#import "CouchbaseLite.h" +#import "CBLTestCommon.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/Tests/Util/CBLJSONUtil.h b/Objective-C/Tests/Util/CBLJSONUtil.h new file mode 100644 index 000000000..4c920d98f --- /dev/null +++ b/Objective-C/Tests/Util/CBLJSONUtil.h @@ -0,0 +1,42 @@ +// +// CBLJSONUtil.h +// CouchbaseLite +// +// Copyright (c) 2026 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** Test-owned JSON utilities. Implemented with Foundation only, independently of + the product's internal implementation, so that they work in both source and + binary test targets and don't reuse the code under test. */ +@interface CBLJSONUtil : NSObject + +/** Formats a date as CouchbaseLite's JSON date representation + (ISO-8601 with milliseconds, e.g. 2026-08-11T12:34:56.789Z). */ ++ (NSString*) jsonDateString: (NSDate*)date; + +/** Parses CouchbaseLite's JSON date representation. */ ++ (nullable NSDate*) dateFromJSONDateString: (NSString*)string; + +/** Parses a JSON string into its Foundation object, for structural comparison + that is insensitive to key order and whitespace. */ ++ (id) jsonObjectFromString: (NSString*)string; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/Tests/Util/CBLJSONUtil.m b/Objective-C/Tests/Util/CBLJSONUtil.m new file mode 100644 index 000000000..52b201389 --- /dev/null +++ b/Objective-C/Tests/Util/CBLJSONUtil.m @@ -0,0 +1,53 @@ +// +// CBLJSONUtil.m +// CouchbaseLite +// +// Copyright (c) 2026 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "CBLJSONUtil.h" + +@implementation CBLJSONUtil + +static NSDateFormatter* jsonDateFormatter(void) { + static NSDateFormatter* sFormatter; + static dispatch_once_t once; + dispatch_once(&once, ^{ + sFormatter = [[NSDateFormatter alloc] init]; + sFormatter.dateFormat = @"uuuu-MM-dd'T'HH:mm:ss.SSSXXX"; + sFormatter.calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSCalendarIdentifierGregorian]; + sFormatter.locale = [NSLocale localeWithLocaleIdentifier: @"en_US_POSIX"]; + sFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT: 0]; + }); + return sFormatter; +} + ++ (NSString*) jsonDateString: (NSDate*)date { + return [jsonDateFormatter() stringFromDate: date]; +} + ++ (NSDate*) dateFromJSONDateString: (NSString*)string { + return [jsonDateFormatter() dateFromString: string]; +} + ++ (id) jsonObjectFromString: (NSString*)string { + NSData* data = [string dataUsingEncoding: NSUTF8StringEncoding]; + NSError* error; + id object = [NSJSONSerialization JSONObjectWithData: data options: 0 error: &error]; + NSCAssert(!error, @"Invalid JSON: %@", error); + return object; +} + +@end diff --git a/Objective-C/Tests/Util/CBLMockConnection.h b/Objective-C/Tests/Util/CBLMockConnection.h index 92018ee4b..35d1fdc1d 100644 --- a/Objective-C/Tests/Util/CBLMockConnection.h +++ b/Objective-C/Tests/Util/CBLMockConnection.h @@ -16,8 +16,7 @@ // limitations under the License. // -#import "CBLMessageEndpointConnection.h" -#import "CBLProtocolType.h" +#import "CBLTestCommon.h" @protocol CBLMockConnectionErrorLogic; @class CBLMessageEndpointListener; @class CBLMockServerConnection; diff --git a/Objective-C/Tests/Util/CBLMockConnection.m b/Objective-C/Tests/Util/CBLMockConnection.m index 74e641121..25d5218d9 100644 --- a/Objective-C/Tests/Util/CBLMockConnection.m +++ b/Objective-C/Tests/Util/CBLMockConnection.m @@ -17,13 +17,8 @@ // #import "CBLMockConnection.h" -#import "CBLProtocolType.h" -#import "CBLMessageEndpointConnection.h" #import "CBLMockConnectionErrorLogic.h" -#import "CBLMessage.h" -#import "CBLMessageEndpoint.h" #import "CollectionUtils.h" -#import "CBLMessageEndpointListener.h" @implementation CBLMockConnection diff --git a/Objective-C/Tests/Util/CBLMockConnectionErrorLogic.m b/Objective-C/Tests/Util/CBLMockConnectionErrorLogic.m index 368dede03..6313afd82 100644 --- a/Objective-C/Tests/Util/CBLMockConnectionErrorLogic.m +++ b/Objective-C/Tests/Util/CBLMockConnectionErrorLogic.m @@ -17,8 +17,7 @@ // #import "CBLMockConnectionErrorLogic.h" -#import "CBLMessagingError.h" -#import "CBLErrors.h" +#import "CBLTestCommon.h" @implementation CBLNoErrorLogic diff --git a/Objective-C/Tests/Util/CBLTestCustomLogSink.h b/Objective-C/Tests/Util/CBLTestCustomLogSink.h index dead6e0c5..a53ede934 100644 --- a/Objective-C/Tests/Util/CBLTestCustomLogSink.h +++ b/Objective-C/Tests/Util/CBLTestCustomLogSink.h @@ -18,7 +18,7 @@ // #import -#import "CBLCustomLogSink.h" +#import "CBLTestCommon.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/Tests/Util/CBLWordEmbeddingModel.h b/Objective-C/Tests/Util/CBLWordEmbeddingModel.h index 6aeee57f9..f764e2144 100644 --- a/Objective-C/Tests/Util/CBLWordEmbeddingModel.h +++ b/Objective-C/Tests/Util/CBLWordEmbeddingModel.h @@ -6,8 +6,7 @@ // COUCHBASE CONFIDENTIAL -- part of Couchbase Lite Enterprise Edition // -#import "CouchbaseLite.h" -#import "CBLPrediction.h" +#import "CBLTestCommon.h" NS_ASSUME_NONNULL_BEGIN diff --git a/Objective-C/Tests/VectorSearchTest+Lazy.m b/Objective-C/Tests/VectorSearchTest+Lazy.m index e9967600e..b27bcd94a 100644 --- a/Objective-C/Tests/VectorSearchTest+Lazy.m +++ b/Objective-C/Tests/VectorSearchTest+Lazy.m @@ -18,7 +18,7 @@ // #import "VectorSearchTest.h" -#import "CBLJSON.h" +#import "CBLJSONUtil.h" /** * Test Spec: https://github.com/couchbaselabs/couchbase-lite-api/blob/master/spec/tests/T0002-Lazy-Vector-Index.md @@ -642,7 +642,7 @@ - (void) testIndexUpdaterGettingValues { [self.defaultCollection saveDocument: mdoc error: &error]; mdoc = [self createDocument: @"doc-5"]; - [mdoc setValue: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"] forKey: @"value"]; + [mdoc setValue: [CBLJSONUtil dateFromJSONDateString: @"2024-05-10T00:00:00.000Z"] forKey: @"value"]; [self.defaultCollection saveDocument: mdoc error: &error]; mdoc = [self createDocument: @"doc-6"]; @@ -746,7 +746,7 @@ - (void) testIndexUpdaterGettingValues { AssertEqual([updater dateAtIndex: 2], nil); AssertEqual([updater dateAtIndex: 3], nil); AssertEqual([updater dateAtIndex: 4], nil); - Assert([[updater dateAtIndex: 5] isEqual: [CBLJSON dateWithJSONObject: @"2024-05-10T00:00:00.000Z"]]); + Assert([[updater dateAtIndex: 5] isEqual: [CBLJSONUtil dateFromJSONDateString: @"2024-05-10T00:00:00.000Z"]]); AssertEqual([updater dateAtIndex: 6], nil); AssertEqual([updater dateAtIndex: 7], nil); AssertEqual([updater dateAtIndex: 8], nil); diff --git a/Objective-C/Tests/iOS/AppDelegate.m b/Objective-C/Tests/iOS/AppDelegate.m index 495253fff..1f3559e3e 100644 --- a/Objective-C/Tests/iOS/AppDelegate.m +++ b/Objective-C/Tests/iOS/AppDelegate.m @@ -18,7 +18,9 @@ // #import "AppDelegate.h" +#ifdef CBL_PERF_TESTS #import "TunesPerfTest.h" +#endif @interface AppDelegate () @@ -32,7 +34,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; [defaults setBool: YES forKey: @"hostApp"]; [defaults synchronize]; -#ifndef DEBUG +#if defined(CBL_PERF_TESTS) && !defined(DEBUG) // In an optimized build, run the performance tests: [TunesPerfTest runWithConfig: nil]; exit(0); diff --git a/Scripts/download_vector_search_extension.sh b/Scripts/download_vector_search_extension.sh new file mode 100755 index 000000000..1719efc13 --- /dev/null +++ b/Scripts/download_vector_search_extension.sh @@ -0,0 +1,51 @@ +#!/bin/bash -e + +# +# Downloads the vector search extension for tests based on the version specified +# in Tests/Extensions/version.txt. The extension will be stored in the +# Tests/Extensions folder. The script will not download the extension if the +# extension of the specified version has already been downloaded. +# +# Note : Downloading a non-release build requires Couchbase VPN. + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +EXTENSIONS_DIR="${SCRIPT_DIR}/../Tests/Extensions" + +pushd "${EXTENSIONS_DIR}" > /dev/null +EXTENSIONS_DIR=`pwd` + +VS_VERSION_FILE="${EXTENSIONS_DIR}/version.txt" +VERSION_NUMBER=$(cat ${VS_VERSION_FILE}) +VS_XCFRAMEWORK_FILE="${EXTENSIONS_DIR}/CouchbaseLiteVectorSearch.xcframework" +VS_DOWNLOADED_VERSION_FILE="${EXTENSIONS_DIR}/.downloaded-version" + +# Skip when the specified version has already been downloaded: +if [ -d "${VS_XCFRAMEWORK_FILE}" ] && [ -f "${VS_DOWNLOADED_VERSION_FILE}" ] && \ + [ "$(cat ${VS_DOWNLOADED_VERSION_FILE})" == "${VERSION_NUMBER}" ]; then + echo "Vector Search Framework ${VERSION_NUMBER} is up to date." + popd > /dev/null + exit 0 +fi + +if [[ "$VERSION_NUMBER" == *"-"* ]]; then + VERSION="${VERSION_NUMBER%-*}" + BLD_NUM="${VERSION_NUMBER##*-}" + ZIP_FILENAME="couchbase-lite-vector-search-${VERSION}-${BLD_NUM}-apple.zip" + URL="https://latestbuilds.service.couchbase.com/builds/latestbuilds/couchbase-lite-vector-search/${VERSION}/${BLD_NUM}/${ZIP_FILENAME}" +else + VERSION="$VERSION_NUMBER" + ZIP_FILENAME="couchbase-lite-vector-search_xcframework_${VERSION}.zip" + URL="https://packages.couchbase.com/releases/couchbase-lite-vector-search/${VERSION}/${ZIP_FILENAME}" +fi + +echo "Download Vector Search Framework from ${URL} ..." +curl -f -O ${URL} + +# Extract the CouchbaseLiteVectorSearch.xcframework: +rm -rf CouchbaseLiteVectorSearch.xcframework +unzip -o ${ZIP_FILENAME} +echo "${VERSION_NUMBER}" > "${VS_DOWNLOADED_VERSION_FILE}" + +rm -rf "${ZIP_FILENAME}" 2> /dev/null + +popd > /dev/null diff --git a/Scripts/prepare_binary_test.sh b/Scripts/prepare_binary_test.sh new file mode 100755 index 000000000..8c50f725f --- /dev/null +++ b/Scripts/prepare_binary_test.sh @@ -0,0 +1,88 @@ +#!/bin/bash -e + +# Stages binary CouchbaseLite frameworks for the binary test targets. +# +# The binary test targets (CBL_*_Binary_Tests) have no dependency on the framework +# targets: they compile and link against the frameworks staged in +# BinaryTests/Frameworks, so that a test run always certifies a specific, +# known binary. This script downloads the specified CouchbaseLite release or +# internal build and stages its macOS framework. The CouchbaseLiteVectorSearch +# extension is staged as well, using the xcframework in Tests/Extensions +# (downloaded per Tests/Extensions/version.txt when needed). +# +# Note : Downloading requires Couchbase VPN. +# +# Usage: +# prepare_binary_test.sh [-] +# +# Examples: +# Scripts/prepare_binary_test.sh objc ee 4.2.0-3 (internal build) +# Scripts/prepare_binary_test.sh objc ce 4.1.0 (release) + +PLATFORM="$1" +EDITION="$2" +VERSION_BUILD="$3" + +case "$PLATFORM" in + objc|swift) ;; + *) sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; +esac + +case "$EDITION" in + ce) EDITION_NAME="community" ;; + ee) EDITION_NAME="enterprise" ;; + *) sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 2 ;; +esac + +if [ -z "$VERSION_BUILD" ]; then + sed -n '/^# Usage:/,/^$/p' "$0" | sed 's/^# \{0,1\}//'; exit 2 +fi + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT_DIR="$SCRIPT_DIR/.." +DEST="$ROOT_DIR/BinaryTests/Frameworks" +EXTENSIONS_DIR="$ROOT_DIR/Tests/Extensions" + +TMP_DIR=$(mktemp -d) +cleanup() { rm -rf "$TMP_DIR"; } +trap cleanup EXIT + +# Copies the whole xcframework to DEST and prints the staged xcframework's +# name, version, and build number (read from the macOS framework's Info.plist). +function stage_xcframework() { + local xcframework="$1" source="$2" + cp -R "$xcframework" "$DEST/" + + local name plist version build + name=$(basename "$xcframework") + plist=$(find "$DEST/$name" -path "*macos*" -name "Info.plist" | grep -E "framework/(Versions/A/Resources/)?Info.plist" | head -1) + version=$(/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$plist" 2>/dev/null || echo "?") + build=$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$plist" 2>/dev/null || echo "?") + echo " $name version $version-$build from $source" | tee -a "$DEST/info.txt" +} + +# Download the CouchbaseLite zip: +if [[ "$VERSION_BUILD" == *"-"* ]]; then + VERSION="${VERSION_BUILD%-*}" + BLD_NUM="${VERSION_BUILD##*-}" + ZIP_FILENAME="couchbase-lite-${PLATFORM}_xc_${EDITION_NAME}_${VERSION}-${BLD_NUM}.zip" + URL="https://latestbuilds.service.couchbase.com/builds/latestbuilds/couchbase-lite-ios/${VERSION}/${BLD_NUM}/${ZIP_FILENAME}" +else + ZIP_FILENAME="couchbase-lite-${PLATFORM}_xc_${EDITION_NAME}_${VERSION_BUILD}.zip" + URL="https://latestbuilds.service.couchbase.com/builds/releases/mobile/couchbase-lite-ios/${VERSION_BUILD}/${ZIP_FILENAME}" +fi +echo "Download CouchbaseLite from ${URL} ..." +curl -f -o "$TMP_DIR/$ZIP_FILENAME" "$URL" +unzip -q "$TMP_DIR/$ZIP_FILENAME" -d "$TMP_DIR/cbl" + +# Download the vector search extension when needed: +"$SCRIPT_DIR/download_vector_search_extension.sh" + +# Stage the xcframeworks: +rm -rf "$DEST" +mkdir -p "$DEST" +echo "Staged for binary testing in BinaryTests/Frameworks:" | tee "$DEST/info.txt" +for xcf in $(find "$TMP_DIR/cbl" -name "*.xcframework" -type d); do + stage_xcframework "$xcf" "$ZIP_FILENAME" +done +stage_xcframework "$EXTENSIONS_DIR/CouchbaseLiteVectorSearch.xcframework" "Tests/Extensions ($(cat "$EXTENSIONS_DIR/version.txt"))" diff --git a/Objective-C/Internal/Foundation+CBL.h b/xcconfigs/CBL_EE_ObjC_Binary_Tests.xcconfig similarity index 69% rename from Objective-C/Internal/Foundation+CBL.h rename to xcconfigs/CBL_EE_ObjC_Binary_Tests.xcconfig index 705cf7e03..313744463 100644 --- a/Objective-C/Internal/Foundation+CBL.h +++ b/xcconfigs/CBL_EE_ObjC_Binary_Tests.xcconfig @@ -1,8 +1,8 @@ // -// Foundation+CBL.h +// CBL_EE_ObjC_Binary_Tests.xcconfig // CouchbaseLite // -// Copyright (c) 2020 Couchbase, Inc All rights reserved. +// Copyright (c) 2026 Couchbase, Inc All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,15 +17,5 @@ // limitations under the License. // -#import -#import "c4Base.h" - -NS_ASSUME_NONNULL_BEGIN - -@interface NSString (CBL) - -// for unit testing purpose only -- (id) toJSONObj; -@end - -NS_ASSUME_NONNULL_END +#include "CBL_EE_Common.xcconfig" +#include "CBL_ObjC_Binary_Tests.xcconfig" diff --git a/xcconfigs/CBL_ObjC_Binary_Tests.xcconfig b/xcconfigs/CBL_ObjC_Binary_Tests.xcconfig new file mode 100644 index 000000000..7c659f7a9 --- /dev/null +++ b/xcconfigs/CBL_ObjC_Binary_Tests.xcconfig @@ -0,0 +1,62 @@ +// +// CBL_ObjC_Binary_Tests.xcconfig +// CouchbaseLite +// +// Copyright (c) 2026 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "CBL_OS_Target_Versions.xcconfig" + +// Black-box binary test suite: compiles against the public CouchbaseLite API +// only. The target has no dependency on the framework target; it compiles and +// links against a staged binary framework in BinaryTests/Frameworks, populated by +// Scripts/prepare_binary_test.sh. Deliberately has no internal or vendor +// (LiteCore/fleece) header search paths — any internal import is a compile error. + +// Marks the target as a binary test suite. Test code guards white-box sections +// with #ifndef CBL_BINARY_TEST, and CBLTestCommon.h imports the framework +// umbrella accordingly. Set here rather than detected in code, so that a target +// missing it fails to build instead of silently dropping the guarded tests. +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) CBL_BINARY_TEST=1 + +GENERATE_INFOPLIST_FILE = YES +PRODUCT_BUNDLE_IDENTIFIER = com.couchbase.CouchbaseLiteBinaryTests +PRODUCT_NAME = CouchbaseLiteBinaryTests + +// Test code is not held to warnings-as-errors, same as the other test targets. +GCC_TREAT_WARNINGS_AS_ERRORS = NO + +// The staged binary xcframeworks in BinaryTests/Frameworks (see +// Scripts/prepare_binary_test.sh) are linked and embedded via the target's +// build phases; the embedded frameworks are loaded from the runpath below. +LD_RUNPATH_SEARCH_PATHS = $(inherited) @loader_path/Frameworks + +// Guarantee at compile time that the binary tests use only public API: without +// this, Xcode's project-wide header map would let quoted imports resolve +// internal headers, bypassing the search paths. +USE_HEADERMAP = NO + +// Test-owned and utility directories only — never product header directories. +HEADER_SEARCH_PATHS = $(SRCROOT)/Objective-C/Tests $(SRCROOT)/Objective-C/Tests/Util $(SRCROOT)/vendor/MYUtilities + +// iOS runs app-hosted: keychain APIs fail with errSecMissingEntitlement +// (-34018) in a hostless test runner, so the keychain tests need a real app +// process. macOS has no such restriction and runs hostless. BUNDLE_LOADER is +// deliberately unset — this bundle links the staged framework itself rather +// than resolving symbols through the host. +TEST_HOST[sdk=iphone*] = $(BUILT_PRODUCTS_DIR)/CBL_Binary_Tests.app/CBL_Binary_Tests + +// Signed for device runs like the other iOS test bundles; macOS needs no identity. +CODE_SIGN_IDENTITY[sdk=iphone*] = iPhone Developer diff --git a/xcconfigs/CBL_ObjC_Binary_Tests_iOS_App.xcconfig b/xcconfigs/CBL_ObjC_Binary_Tests_iOS_App.xcconfig new file mode 100644 index 000000000..a2588813a --- /dev/null +++ b/xcconfigs/CBL_ObjC_Binary_Tests_iOS_App.xcconfig @@ -0,0 +1,44 @@ +// +// CBL_ObjC_Binary_Tests_iOS_App.xcconfig +// CouchbaseLite +// +// Copyright (c) 2026 Couchbase, Inc All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "CBL_OS_Target_Versions.xcconfig" + +// Host application for the iOS binary tests. It exists only to give the test +// bundle a real app process: iOS keychain APIs fail with +// errSecMissingEntitlement (-34018) in a hostless test runner, and this app's +// AppDelegate sets the "hostApp" default that ungates the keychain tests. +// +// It deliberately links no frameworks. The test bundle embeds and loads the +// staged binary itself, so a second copy embedded here would put two +// CouchbaseLite images in one process. + +// iOS only: the project-wide SUPPORTED_PLATFORMS also lists macOS, which would +// make a macOS build of the test bundle try to build this app as a dependency. +SDKROOT = iphoneos +SUPPORTED_PLATFORMS = iphoneos iphonesimulator + +// Test support code is not held to warnings-as-errors, same as the test targets. +GCC_TREAT_WARNINGS_AS_ERRORS = NO +GENERATE_INFOPLIST_FILE = NO +INFOPLIST_FILE = $(SRCROOT)/Objective-C/Tests/iOS/Info.plist +ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon +CODE_SIGN_IDENTITY = iPhone Developer +CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO +PRODUCT_BUNDLE_IDENTIFIER = com.couchbase.cbl-ios-binary-tests-app +PRODUCT_NAME = CBL_Binary_Tests diff --git a/xcconfigs/CBL_ObjC_Tests_iOS_App.xcconfig b/xcconfigs/CBL_ObjC_Tests_iOS_App.xcconfig index 1a7f47af9..20287c90f 100644 --- a/xcconfigs/CBL_ObjC_Tests_iOS_App.xcconfig +++ b/xcconfigs/CBL_ObjC_Tests_iOS_App.xcconfig @@ -20,6 +20,10 @@ #include "CBL_OS_Target_Versions.xcconfig" HEADER_SEARCH_PATHS = $(SRCROOT)/vendor/couchbase-lite-core/vendor/fleece/API $(SRCROOT)/vendor/couchbase-lite-core/vendor/fleece/Fleece/Support +// This host app runs the performance tests in optimized builds; the binary +// test host app does not, and so links none of the perf test sources. +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) CBL_PERF_TESTS=1 + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = NO CODE_SIGN_IDENTITY = iPhone Developer