From e64f032e2f137c3aef2bc3f8d40b8cf547057ce7 Mon Sep 17 00:00:00 2001 From: Pasin Suriyentrakorn Date: Mon, 17 Aug 2026 20:40:48 -0700 Subject: [PATCH] CBL-8743 : Add binary test targets for the Objective-C test suites Runs the Objective-C tests against a shipped CouchbaseLite binary instead of a build from the working tree, so a test run certifies a released artifact. - Add CBL_EE_ObjC_Binary_Tests and CBL_ObjC_Binary_Tests, which compile and link against frameworks staged in BinaryTests/Frameworks. Each runs on macOS and iOS. The community target leaves out the tests for Enterprise edition. - Add Scripts/prepare_binary_test.sh to download and copy built binaries from latestbuild. - Move download_vector_search_extension.sh into CE repo from the EE repo as it is used by prepare_binary_tests.sh to dowload the vector search extension. Update the script to detect and skip downloading if the extension's already downloaded. - Set CBL_BINARY_TEST in the targets' xcconfig. Tests that need internal API are guarded with #ifndef CBL_BINARY_TEST. - Limit the targets to the public API. They have no internal header search paths, and USE_HEADERMAP = NO so that a stray internal import fails to compile. - Replace internal API in the shared tests with public equivalents: a CBLJSONUtil owned by the tests, SecItem keychain cleanup, and a N1QL query on meta().deleted for document expiration. - Delete internal API that no test uses now: Foundation+CBL, activeServiceCount, isClosedLocked and the fromCollections:config: initializer. - Add an iOS host application, shared by both editions. iOS tests need one because keychain calls fail without an app, and its AppDelegate sets the default that enables the keychain tests. It links no frameworks, so the only CouchbaseLite loaded is the one embedded in the test bundle. - Add a scheme per edition and platform. The macOS ones run without a host app. The community targets build in Debug and Release and the enterprise ones in Debug_EE and Release_EE, which keeps their products apart. --- .gitignore | 4 +- CouchbaseLite.xcodeproj/project.pbxproj | 802 +++++++++++++++++- .../CBL_EE_ObjC_Binary_Tests.xcscheme | 55 ++ .../CBL_EE_ObjC_Binary_Tests_iOS_App.xcscheme | 85 ++ .../xcschemes/CBL_ObjC_Binary_Tests.xcscheme | 55 ++ .../CBL_ObjC_Binary_Tests_iOS_App.xcscheme | 85 ++ Objective-C/CBLCollectionConfiguration.m | 14 - Objective-C/CBLDatabase.mm | 13 - Objective-C/Internal/CBLDatabase+Internal.h | 2 - .../CBLCollectionConfiguration+Internal.h | 14 - Objective-C/Tests/ArrayTest.m | 9 +- Objective-C/Tests/AuthenticatorTest.m | 43 +- Objective-C/Tests/CBLTestCase.h | 7 +- Objective-C/Tests/CBLTestCase.m | 40 +- .../CBLTestCommon.h} | 27 +- Objective-C/Tests/ConcurrentTest.m | 19 +- Objective-C/Tests/DatabaseEncryptionTest.m | 1 - Objective-C/Tests/DatabaseTest.m | 64 +- Objective-C/Tests/DictionaryTest.m | 11 +- Objective-C/Tests/DocumentExpirationTest.m | 107 +-- Objective-C/Tests/DocumentTest.m | 51 +- Objective-C/Tests/FragmentTest.m | 12 +- Objective-C/Tests/LogTest.m | 7 + Objective-C/Tests/MigrationTest.m | 8 +- Objective-C/Tests/MiscCppTest.mm | 7 + Objective-C/Tests/MiscTest.m | 7 + Objective-C/Tests/MultipeerReplicatorTest.m | 7 + Objective-C/Tests/NotificationTest.m | 1 - .../Tests/PredictiveQueryTest+CoreML.m | 98 ++- Objective-C/Tests/PredictiveQueryTest.m | 8 +- Objective-C/Tests/QueryTest+Main.m | 352 ++++---- .../Tests/ReplicatorTest+Backgrounding.m | 334 ++++++++ Objective-C/Tests/ReplicatorTest+Collection.m | 17 +- .../Tests/ReplicatorTest+CustomConflict.m | 406 ++++----- Objective-C/Tests/ReplicatorTest+Main.m | 540 +++--------- .../Tests/ReplicatorTest+MessageEndPoint.m | 4 - .../Tests/ReplicatorTest+PendingDocIds.m | 1 - Objective-C/Tests/ReplicatorTest+SG.m | 138 +++ Objective-C/Tests/ReplicatorTest.h | 17 +- Objective-C/Tests/ReplicatorTest.m | 135 --- Objective-C/Tests/TLSIdentityTest.m | 11 + Objective-C/Tests/TrustCheckTest.m | 7 + .../Tests/URLEndpointListenerTest+Main.m | 117 ++- Objective-C/Tests/URLEndpointListenerTest.h | 12 +- Objective-C/Tests/URLEndpointListenerTest.m | 93 +- Objective-C/Tests/UnnestArrayIndexTest.m | 24 +- .../Tests/Util/CBLBlockConflictResolver.h | 2 +- Objective-C/Tests/Util/CBLJSONUtil.h | 42 + Objective-C/Tests/Util/CBLJSONUtil.m | 53 ++ Objective-C/Tests/Util/CBLMockConnection.h | 3 +- Objective-C/Tests/Util/CBLMockConnection.m | 5 - .../Tests/Util/CBLMockConnectionErrorLogic.m | 3 +- Objective-C/Tests/Util/CBLTestCustomLogSink.h | 2 +- .../Tests/Util/CBLWordEmbeddingModel.h | 3 +- Objective-C/Tests/VectorSearchTest+Lazy.m | 6 +- Objective-C/Tests/iOS/AppDelegate.m | 4 +- Scripts/download_vector_search_extension.sh | 51 ++ Scripts/prepare_binary_test.sh | 88 ++ .../CBL_EE_ObjC_Binary_Tests.xcconfig | 18 +- xcconfigs/CBL_ObjC_Binary_Tests.xcconfig | 62 ++ .../CBL_ObjC_Binary_Tests_iOS_App.xcconfig | 44 + xcconfigs/CBL_ObjC_Tests_iOS_App.xcconfig | 4 + 62 files changed, 2864 insertions(+), 1397 deletions(-) create mode 100644 CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests.xcscheme create mode 100644 CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_EE_ObjC_Binary_Tests_iOS_App.xcscheme create mode 100644 CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests.xcscheme create mode 100644 CouchbaseLite.xcodeproj/xcshareddata/xcschemes/CBL_ObjC_Binary_Tests_iOS_App.xcscheme rename Objective-C/{Internal/Foundation+CBL.mm => Tests/CBLTestCommon.h} (55%) create mode 100644 Objective-C/Tests/ReplicatorTest+Backgrounding.m create mode 100644 Objective-C/Tests/Util/CBLJSONUtil.h create mode 100644 Objective-C/Tests/Util/CBLJSONUtil.m create mode 100755 Scripts/download_vector_search_extension.sh create mode 100755 Scripts/prepare_binary_test.sh rename Objective-C/Internal/Foundation+CBL.h => xcconfigs/CBL_EE_ObjC_Binary_Tests.xcconfig (69%) create mode 100644 xcconfigs/CBL_ObjC_Binary_Tests.xcconfig create mode 100644 xcconfigs/CBL_ObjC_Binary_Tests_iOS_App.xcconfig 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