diff --git a/.github/actions/ci/action.yml b/.github/actions/ci/action.yml index b30547be..b23f9318 100644 --- a/.github/actions/ci/action.yml +++ b/.github/actions/ci/action.yml @@ -29,15 +29,25 @@ runs: run: | brew install mint + - name: Install swiftlint + shell: bash + run: | + brew install swiftlint + - name: Install cocoapods shell: bash run: gem install cocoapods + - name: Install xcpretty + shell: bash + run: | + gem install xcpretty + - name: Lint the podspec shell: bash run: pod lib lint LaunchDarkly.podspec --allow-warnings - - name: Run swiftlint + - name: Run swiftlint on ContractTests shell: bash run: | cd ./ContractTests @@ -71,6 +81,30 @@ runs: shell: bash run: xcodebuild build -scheme 'LaunchDarkly_watchOS' -sdk watchos | xcpretty + - name: Display SwiftLint output (if build failed) + shell: bash + if: failure() + run: | + if [ -f swiftlint_build_output.log ]; then + echo "=== SwiftLint Build Script Output ===" + cat swiftlint_build_output.log + echo "=== End SwiftLint Output ===" + else + echo "No SwiftLint log file found" + fi + + - name: Display Sourcery output (if build failed) + shell: bash + if: failure() + run: | + if [ -f sourcery_build_output.log ]; then + echo "=== Sourcery Build Script Output ===" + cat sourcery_build_output.log + echo "=== End Sourcery Output ===" + else + echo "No Sourcery log file found" + fi + - name: Build & Test with swiftpm shell: bash run: swift test -v diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 7485979d..b594f3d2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,7 +1,7 @@ **Requirements** - [ ] I have added test coverage for new or changed functionality -- [ ] I have followed the repository's [pull request submission guidelines](../blob/v9/CONTRIBUTING.md#submitting-pull-requests) +- [ ] I have followed the repository's [pull request submission guidelines](../blob/v10/CONTRIBUTING.md#submitting-pull-requests) - [ ] I have validated my changes against all supported platform versions **Related issues** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f7dac12..51af6e9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,11 +1,11 @@ name: Run CI on: push: - branches: [ v9, 'feat/**' ] + branches: [ v10, 'feat/**' ] paths-ignore: - '**.md' # Do not need to run CI for markdown changes. pull_request: - branches: [ v9, 'feat/**' ] + branches: [ v10, 'feat/**' ] paths-ignore: - '**.md' @@ -17,13 +17,13 @@ jobs: fail-fast: false matrix: include: - - xcode-version: 15.0.1 - ios-sim: 'platform=iOS Simulator,name=iPhone 15,OS=17.2' - os: macos-13 + - xcode-version: 16.4.0 + ios-sim: 'platform=iOS Simulator,name=iPhone 16' + os: macos-15 run-contract-tests: true - - xcode-version: 14.3.1 - ios-sim: 'platform=iOS Simulator,name=iPhone 14,OS=16.4' - os: macos-13 + - xcode-version: 15.4.0 + ios-sim: 'platform=iOS Simulator,name=iPhone 15' + os: macos-14 run-contract-tests: false steps: diff --git a/.github/workflows/manual-publish-docs.yml b/.github/workflows/manual-publish-docs.yml index 125a4710..f1002c55 100644 --- a/.github/workflows/manual-publish-docs.yml +++ b/.github/workflows/manual-publish-docs.yml @@ -4,7 +4,7 @@ on: name: Publish Documentation jobs: build-publish: - runs-on: macos-14 + runs-on: macos-15 permissions: id-token: write # Needed if using OIDC to get release secrets. @@ -16,8 +16,8 @@ jobs: - name: Build and Test uses: ./.github/actions/ci with: - xcode-version: 15.0.1 - ios-sim: 'platform=iOS Simulator,name=iPhone 15,OS=17.2' + xcode-version: 16.4.0 + ios-sim: 'platform=iOS Simulator,name=iPhone 15' run-contract-tests: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/manual-publish.yml b/.github/workflows/manual-publish.yml index 533bf330..bc65be5d 100644 --- a/.github/workflows/manual-publish.yml +++ b/.github/workflows/manual-publish.yml @@ -9,7 +9,7 @@ on: jobs: build-publish: - runs-on: macos-14 + runs-on: macos-15 # Needed to get tokens during publishing. permissions: @@ -27,8 +27,8 @@ jobs: - uses: ./.github/actions/ci with: - xcode-version: 15.0.1 - ios-sim: 'platform=iOS Simulator,name=iPhone 15,OS=17.2' + xcode-version: 16.4.0 + ios-sim: 'platform=iOS Simulator,name=iPhone 15' run-contract-tests: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index cfa5e668..ed85c412 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -4,11 +4,11 @@ on: workflow_dispatch: push: branches: - - v9 + - v10 jobs: release-package: - runs-on: macos-14 + runs-on: macos-15 permissions: id-token: write # Needed if using OIDC to get release secrets. @@ -46,8 +46,8 @@ jobs: - uses: ./.github/actions/ci if: ${{ steps.release.outputs.releases_created == 'true' }} with: - xcode-version: 15.0.1 - ios-sim: 'platform=iOS Simulator,name=iPhone 15,OS=17.2' + xcode-version: 16.4.0 + ios-sim: 'platform=iOS Simulator,name=iPhone 15' run-contract-tests: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/ContractTests/Package.swift b/ContractTests/Package.swift index 59c2b8e8..82d0d3d9 100644 --- a/ContractTests/Package.swift +++ b/ContractTests/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.5 import PackageDescription @@ -6,7 +6,7 @@ let package = Package( name: "ContractTests", platforms: [ .iOS(.v12), - .macOS(.v10_15), + .macOS(.v12), .watchOS(.v4), .tvOS(.v12) ], diff --git a/LaunchDarkly.xcodeproj/project.pbxproj b/LaunchDarkly.xcodeproj/project.pbxproj index e4ced8f8..ff4bbabf 100644 --- a/LaunchDarkly.xcodeproj/project.pbxproj +++ b/LaunchDarkly.xcodeproj/project.pbxproj @@ -303,7 +303,16 @@ A3BA7CF42BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF22BD05A280000DB28 /* EvaluationSeriesContext.swift */; }; A3BA7CF52BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF22BD05A280000DB28 /* EvaluationSeriesContext.swift */; }; A3BA7CF62BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF22BD05A280000DB28 /* EvaluationSeriesContext.swift */; }; - A3BA7D022BD192240000DB28 /* LDClientHookSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7D012BD192240000DB28 /* LDClientHookSpec.swift */; }; + A3BA7CF72BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */; }; + A3BA7CF92BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */; }; + A3BA7CFA2BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */; }; + A3BA7CFB2BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */; }; + A3BA7CFC2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */; }; + A3BA7CFE2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */; }; + A3BA7CFF2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */; }; + A3BA7D002BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */; }; + A3BA7D072BD192250000DB28 /* LDClientEvaluationHookSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7D052BD192250000DB28 /* LDClientEvaluationHookSpec.swift */; }; + A3BA7D082BD192250000DB28 /* LDClientIdentifyHookSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7D062BD192250000DB28 /* LDClientIdentifyHookSpec.swift */; }; A3BA7D042BD2BD620000DB28 /* TestContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3BA7D032BD2BD620000DB28 /* TestContext.swift */; }; A3C6F7622B7FA803005B3B61 /* SheddingQueueSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C6F7612B7FA803005B3B61 /* SheddingQueueSpec.swift */; }; A3C6F7642B84EF0C005B3B61 /* IdentifyTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3C6F7632B84EF0C005B3B61 /* IdentifyTypes.swift */; }; @@ -537,7 +546,10 @@ A3BA7CE82BD056920000DB28 /* Hook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hook.swift; sourceTree = ""; }; A3BA7CED2BD059180000DB28 /* Metadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Metadata.swift; sourceTree = ""; }; A3BA7CF22BD05A280000DB28 /* EvaluationSeriesContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EvaluationSeriesContext.swift; sourceTree = ""; }; - A3BA7D012BD192240000DB28 /* LDClientHookSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDClientHookSpec.swift; sourceTree = ""; }; + A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifySeriesContext.swift; sourceTree = ""; }; + A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDClientIdentifyHook.swift; sourceTree = ""; }; + A3BA7D052BD192250000DB28 /* LDClientEvaluationHookSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDClientEvaluationHookSpec.swift; sourceTree = ""; }; + A3BA7D062BD192250000DB28 /* LDClientIdentifyHookSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LDClientIdentifyHookSpec.swift; sourceTree = ""; }; A3BA7D032BD2BD620000DB28 /* TestContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestContext.swift; sourceTree = ""; }; A3C6F7612B7FA803005B3B61 /* SheddingQueueSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheddingQueueSpec.swift; sourceTree = ""; }; A3C6F7632B84EF0C005B3B61 /* IdentifyTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifyTypes.swift; sourceTree = ""; }; @@ -723,6 +735,7 @@ A380B0982B60178D00AB64A6 /* PrivacyInfo.xcprivacy */, 83B6C4B51F4DE7630055351C /* LDCommon.swift */, 8354EFDC1F26380700C05156 /* LDClient.swift */, + A3BA7CFD2BD05A2A0000DB28 /* LDClientIdentifyHook.swift */, B495A8A12787762C0051977C /* LDClientVariation.swift */, 29FE1297280413D4008CC918 /* Util.swift */, 8354EFE61F263E4200C05156 /* Models */, @@ -741,7 +754,8 @@ isa = PBXGroup; children = ( 3D2406242E0D90EA00F91253 /* LDClientPluginsSpec.swift */, - A3BA7D012BD192240000DB28 /* LDClientHookSpec.swift */, + A3BA7D052BD192250000DB28 /* LDClientEvaluationHookSpec.swift */, + A3BA7D062BD192250000DB28 /* LDClientIdentifyHookSpec.swift */, 838F96731FB9F024009CFC45 /* LDClientSpec.swift */, 3D9A12572A73236800698B8D /* UtilSpec.swift */, 83EF67911F9945CE00403126 /* Models */, @@ -972,6 +986,7 @@ A3BA7CE82BD056920000DB28 /* Hook.swift */, A3BA7CED2BD059180000DB28 /* Metadata.swift */, A3BA7CF22BD05A280000DB28 /* EvaluationSeriesContext.swift */, + A3BA7CF82BD05A290000DB28 /* IdentifySeriesContext.swift */, ); path = Hooks; sourceTree = ""; @@ -1273,7 +1288,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n"; + shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\n# Write to a file in the workspace that persists after build\nLOG_FILE=\"${SRCROOT}/swiftlint_build_output.log\"\n\necho \"[SwiftLint] Starting SwiftLint check...\" >> \"$LOG_FILE\"\necho \"[SwiftLint] PATH: $PATH\" >> \"$LOG_FILE\"\necho \"[SwiftLint] Working directory: $(pwd)\" >> \"$LOG_FILE\"\necho \"[SwiftLint] SRCROOT: ${SRCROOT}\" >> \"$LOG_FILE\"\n\n# Try system swiftlint first (installed via brew in CI), fall back to mint\nif command -v swiftlint >/dev/null 2>&1; then\n SWIFTLINT_CMD=$(command -v swiftlint)\n echo \"[SwiftLint] Using system swiftlint at: $SWIFTLINT_CMD\" >> \"$LOG_FILE\"\n set +e\n \"$SWIFTLINT_CMD\" >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelif which mint >/dev/null; then\n echo \"[SwiftLint] Using mint, found at: $(which mint)\" >> \"$LOG_FILE\"\n echo \"[SwiftLint] Running: /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\" >> \"$LOG_FILE\"\n set +e\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelse\n echo \"warning: Neither swiftlint nor mint found. SwiftLint check skipped.\" >> \"$LOG_FILE\"\n echo \"warning: Install swiftlint via 'brew install swiftlint' or mint via 'brew install mint'\"\nfi\n"; }; 830C2AC2207416A5001D645D /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -1288,7 +1303,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n"; + shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\n# Write to a file in the workspace that persists after build\nLOG_FILE=\"${SRCROOT}/swiftlint_build_output.log\"\n\necho \"[SwiftLint] Starting SwiftLint check...\" >> \"$LOG_FILE\"\necho \"[SwiftLint] PATH: $PATH\" >> \"$LOG_FILE\"\necho \"[SwiftLint] Working directory: $(pwd)\" >> \"$LOG_FILE\"\necho \"[SwiftLint] SRCROOT: ${SRCROOT}\" >> \"$LOG_FILE\"\n\n# Try system swiftlint first (installed via brew in CI), fall back to mint\nif command -v swiftlint >/dev/null 2>&1; then\n SWIFTLINT_CMD=$(command -v swiftlint)\n echo \"[SwiftLint] Using system swiftlint at: $SWIFTLINT_CMD\" >> \"$LOG_FILE\"\n set +e\n \"$SWIFTLINT_CMD\" >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelif which mint >/dev/null; then\n echo \"[SwiftLint] Using mint, found at: $(which mint)\" >> \"$LOG_FILE\"\n echo \"[SwiftLint] Running: /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\" >> \"$LOG_FILE\"\n set +e\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelse\n echo \"warning: Neither swiftlint nor mint found. SwiftLint check skipped.\" >> \"$LOG_FILE\"\n echo \"warning: Install swiftlint via 'brew install swiftlint' or mint via 'brew install mint'\"\nfi\n"; }; 833FD9F821C01333001F80EB /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1306,7 +1321,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n"; + shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\n# Write to a file in the workspace that persists after build\nLOG_FILE=\"${SRCROOT}/swiftlint_build_output.log\"\n\necho \"[SwiftLint] Starting SwiftLint check...\" >> \"$LOG_FILE\"\necho \"[SwiftLint] PATH: $PATH\" >> \"$LOG_FILE\"\necho \"[SwiftLint] Working directory: $(pwd)\" >> \"$LOG_FILE\"\necho \"[SwiftLint] SRCROOT: ${SRCROOT}\" >> \"$LOG_FILE\"\n\n# Try system swiftlint first (installed via brew in CI), fall back to mint\nif command -v swiftlint >/dev/null 2>&1; then\n SWIFTLINT_CMD=$(command -v swiftlint)\n echo \"[SwiftLint] Using system swiftlint at: $SWIFTLINT_CMD\" >> \"$LOG_FILE\"\n set +e\n \"$SWIFTLINT_CMD\" >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelif which mint >/dev/null; then\n echo \"[SwiftLint] Using mint, found at: $(which mint)\" >> \"$LOG_FILE\"\n echo \"[SwiftLint] Running: /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\" >> \"$LOG_FILE\"\n set +e\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelse\n echo \"warning: Neither swiftlint nor mint found. SwiftLint check skipped.\" >> \"$LOG_FILE\"\n echo \"warning: Install swiftlint via 'brew install swiftlint' or mint via 'brew install mint'\"\nfi\n"; }; 83411A561FABCA2200E5CF39 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -1320,7 +1335,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run krzysztofzablocki/Sourcery\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n"; + shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\n# Write to a file in the workspace that persists after build\nLOG_FILE=\"${SRCROOT}/sourcery_build_output.log\"\n\necho \"[Sourcery] Starting Sourcery code generation...\" >> \"$LOG_FILE\"\necho \"[Sourcery] Timestamp: $(date)\" >> \"$LOG_FILE\"\necho \"[Sourcery] PATH: $PATH\" >> \"$LOG_FILE\"\necho \"[Sourcery] Working directory: $(pwd)\" >> \"$LOG_FILE\"\necho \"[Sourcery] SRCROOT: ${SRCROOT}\" >> \"$LOG_FILE\"\necho \"[Sourcery] XCODE_PRODUCT_BUILD_VERSION: ${XCODE_PRODUCT_BUILD_VERSION}\" >> \"$LOG_FILE\"\n\nif which mint >/dev/null 2>&1; then\n MINT_CMD=$(which mint)\n echo \"[Sourcery] Using mint, found at: $MINT_CMD\" >> \"$LOG_FILE\"\n echo \"[Sourcery] Running: /usr/bin/xcrun --sdk macosx mint run krzysztofzablocki/Sourcery\" >> \"$LOG_FILE\"\n set +e\n /usr/bin/xcrun --sdk macosx mint run krzysztofzablocki/Sourcery >> \"$LOG_FILE\" 2>&1\n SOURCERY_EXIT_CODE=$?\n set -e\n echo \"[Sourcery] Exit code: $SOURCERY_EXIT_CODE\" >> \"$LOG_FILE\"\n if [ $SOURCERY_EXIT_CODE -ne 0 ]; then\n echo \"[Sourcery] ERROR: Sourcery failed with exit code $SOURCERY_EXIT_CODE\" >> \"$LOG_FILE\"\n echo \"[Sourcery] Check $LOG_FILE for details\"\n else\n echo \"[Sourcery] Successfully completed\" >> \"$LOG_FILE\"\n fi\n exit $SOURCERY_EXIT_CODE\nelse\n echo \"[Sourcery] ERROR: mint not found in PATH\" >> \"$LOG_FILE\"\n echo \"[Sourcery] PATH was: $PATH\" >> \"$LOG_FILE\"\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\" >> \"$LOG_FILE\"\n exit 1\nfi\n"; }; 835E1CFE1F61AC0600184DB4 /* Run Script */ = { isa = PBXShellScriptBuildPhase; @@ -1335,7 +1350,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which mint >/dev/null; then\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\nelse\n echo \"warning: mint not installed, available from https://github.com/yonaskolb/Mint\"\nfi\n"; + shellScript = "# Adds support for Apple Silicon brew directory\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\n# Write to a file in the workspace that persists after build\nLOG_FILE=\"${SRCROOT}/swiftlint_build_output.log\"\n\necho \"[SwiftLint] Starting SwiftLint check...\" >> \"$LOG_FILE\"\necho \"[SwiftLint] PATH: $PATH\" >> \"$LOG_FILE\"\necho \"[SwiftLint] Working directory: $(pwd)\" >> \"$LOG_FILE\"\necho \"[SwiftLint] SRCROOT: ${SRCROOT}\" >> \"$LOG_FILE\"\n\n# Try system swiftlint first (installed via brew in CI), fall back to mint\nif command -v swiftlint >/dev/null 2>&1; then\n SWIFTLINT_CMD=$(command -v swiftlint)\n echo \"[SwiftLint] Using system swiftlint at: $SWIFTLINT_CMD\" >> \"$LOG_FILE\"\n set +e\n \"$SWIFTLINT_CMD\" >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelif which mint >/dev/null; then\n echo \"[SwiftLint] Using mint, found at: $(which mint)\" >> \"$LOG_FILE\"\n echo \"[SwiftLint] Running: /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint\" >> \"$LOG_FILE\"\n set +e\n /usr/bin/xcrun --sdk macosx mint run realm/SwiftLint >> \"$LOG_FILE\" 2>&1\n SWIFTLINT_EXIT_CODE=$?\n set -e\n echo \"[SwiftLint] Exit code: $SWIFTLINT_EXIT_CODE\" >> \"$LOG_FILE\"\n exit $SWIFTLINT_EXIT_CODE\nelse\n echo \"warning: Neither swiftlint nor mint found. SwiftLint check skipped.\" >> \"$LOG_FILE\"\n echo \"warning: Install swiftlint via 'brew install swiftlint' or mint via 'brew install mint'\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -1358,6 +1373,7 @@ 3D2406172E0D90E000F91253 /* SdkMetadata.swift in Sources */, A358D6DA2A4DE6A500270C60 /* ApplicationInfoEnvironmentReporter.swift in Sources */, 831188452113ADC500D77CB5 /* LDClient.swift in Sources */, + A3BA7CFC2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */, A3BA7CF12BD059180000DB28 /* Metadata.swift in Sources */, A310881E2837DC0400184942 /* Kind.swift in Sources */, A310881A2837DC0400184942 /* Reference.swift in Sources */, @@ -1373,6 +1389,7 @@ C43C37E8238DF22D003C1624 /* LDEvaluationDetail.swift in Sources */, 8311884C2113ADDE00D77CB5 /* FlagChangeObserver.swift in Sources */, A3BA7CF62BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */, + A3BA7CFB2BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */, A3470C3A2B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */, C443A41223186A4F00145710 /* ConnectionModeChangeObserver.swift in Sources */, 831188592113AE1200D77CB5 /* FlagStore.swift in Sources */, @@ -1428,6 +1445,7 @@ 831EF34420655E730001C643 /* LDConfig.swift in Sources */, A31088212837DC0400184942 /* LDContext.swift in Sources */, 831EF34520655E730001C643 /* LDClient.swift in Sources */, + A3BA7CFE2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */, A3BA7CF02BD059180000DB28 /* Metadata.swift in Sources */, 830DB3B02239B54900D65D25 /* URLResponse.swift in Sources */, B4C9D4352489C8FD004A9B03 /* DiagnosticCache.swift in Sources */, @@ -1460,6 +1478,7 @@ A358D6F42A4DEB4C00270C60 /* EnvironmentReporterBuilder.swift in Sources */, B4C9D4302489B5FF004A9B03 /* DiagnosticEvent.swift in Sources */, A3BA7CF52BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */, + A3BA7CFA2BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */, 831EF35620655E730001C643 /* FlagChangeNotifier.swift in Sources */, A358D6D92A4DE6A500270C60 /* ApplicationInfoEnvironmentReporter.swift in Sources */, 831EF35720655E730001C643 /* EventReporter.swift in Sources */, @@ -1528,6 +1547,7 @@ 8358F25E1F474E5900ECE1AF /* LDChangedFlag.swift in Sources */, 83D559741FD87CC9002D10C8 /* KeyedValueCache.swift in Sources */, A3BA7CF32BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */, + A3BA7CF92BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */, A3470C372B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */, C43C37E1236BA050003C1624 /* LDEvaluationDetail.swift in Sources */, 831AAE2C20A9E4F600B46DBA /* Throttler.swift in Sources */, @@ -1545,6 +1565,7 @@ A358D6DD2A4DE7D600270C60 /* IOSEnvironmentReporter.swift in Sources */, 83B9A082204F6022000C3F17 /* FlagsUnchangedObserver.swift in Sources */, 8354EFE01F26380700C05156 /* LDClient.swift in Sources */, + A3BA7CFF2BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */, 831425B1206B030100F2EF36 /* EnvironmentReporter.swift in Sources */, C408884723033B3600420721 /* ConnectionInformationStore.swift in Sources */, 83B6C4B61F4DE7630055351C /* LDCommon.swift in Sources */, @@ -1621,7 +1642,8 @@ 50EE85C72EA0749C007CC662 /* TimeoutExecutorSpec.swift in Sources */, 837406D421F760640087B22B /* LDTimerSpec.swift in Sources */, 832307A61F7D8D720029815A /* URLRequestSpec.swift in Sources */, - A3BA7D022BD192240000DB28 /* LDClientHookSpec.swift in Sources */, + A3BA7D072BD192250000DB28 /* LDClientEvaluationHookSpec.swift in Sources */, + A3BA7D082BD192250000DB28 /* LDClientIdentifyHookSpec.swift in Sources */, 832307A81F7DA61B0029815A /* LDEventSourceMock.swift in Sources */, 838F967A1FBA551A009CFC45 /* ClientServiceMockFactory.swift in Sources */, A31088292837DCA900184942 /* KindSpec.swift in Sources */, @@ -1636,6 +1658,7 @@ 83D9EC762062DEAB004D7FA6 /* LDConfig.swift in Sources */, 83EBCBB420DABE1B003A7142 /* FlagRequestTracker.swift in Sources */, 83D9EC772062DEAB004D7FA6 /* LDClient.swift in Sources */, + A3BA7D002BD05A2A0000DB28 /* LDClientIdentifyHook.swift in Sources */, B4C9D4342489C8FD004A9B03 /* DiagnosticCache.swift in Sources */, 83D9EC7C2062DEAB004D7FA6 /* FeatureFlag.swift in Sources */, A36EDFCE2853C50B00D91B05 /* ObjcLDContext.swift in Sources */, @@ -1661,6 +1684,7 @@ A358D6F02A4DE9EB00270C60 /* WatchOSEnvironmentReporter.swift in Sources */, 831AAE2D20A9E4F600B46DBA /* Throttler.swift in Sources */, A3BA7CF42BD05A280000DB28 /* EvaluationSeriesContext.swift in Sources */, + A3BA7CF72BD05A290000DB28 /* IdentifySeriesContext.swift in Sources */, A3470C382B7C1ACE00951CEE /* LDValueDecoder.swift in Sources */, C43C37E6238DF22B003C1624 /* LDEvaluationDetail.swift in Sources */, 83D9EC872062DEAB004D7FA6 /* FlagSynchronizer.swift in Sources */, diff --git a/LaunchDarkly/LaunchDarkly/LDClient.swift b/LaunchDarkly/LaunchDarkly/LDClient.swift index 411de932..682c5b5d 100644 --- a/LaunchDarkly/LaunchDarkly/LDClient.swift +++ b/LaunchDarkly/LaunchDarkly/LDClient.swift @@ -34,8 +34,9 @@ enum LDClientRunMode { self?.updateFlag(key: "flag-key", changedFlag: changedFlag) } ``` - The `changedFlag` passed in to the closure contains the old and new value of the flag. - */ +The `changedFlag` passed in to the closure contains the old and new value of the flag. +*/ +// swiftlint:disable type_body_length missing_docs public class LDClient { // MARK: - State Controls and Indicators @@ -332,6 +333,7 @@ public class LDClient { // Temporary helper method to allow code sharing between the sheddable and unsheddable identify methods. In the next major release, we will remove the deprecated identify method and inline // this implementation in the other one. + // swiftlint:disable:next identifier_name func _identify(context: LDContext, sheddable: Bool, useCache: IdentifyCacheUsage, completion: @escaping (_ result: IdentifyResult) -> Void) { let work: TaskHandler = { taskCompletion in let dispatch = DispatchGroup() @@ -352,7 +354,7 @@ public class LDClient { } identifyQueue.enqueue(request: identifyTask) } - + /** Sets the LDContext into the LDClient inline with the behavior detailed on `LDClient.identify(context: completion:)`. Additionally, this method will ensure the `completion` parameter will be called within the specified time interval. @@ -384,7 +386,7 @@ public class LDClient { if timeout > LDClient.longTimeoutInterval { os_log("%s LDClient.identify was called with a timeout greater than %f seconds. We recommend a timeout of less than %f seconds.", log: config.logger, type: .info, self.typeName(and: #function), LDClient.longTimeoutInterval, LDClient.longTimeoutInterval) } - + self._identifyHooked(context: context, sheddable: true, useCache: useCache, timeout: timeout, completion: completion) } diff --git a/LaunchDarkly/LaunchDarkly/LDClientIdentifyHook.swift b/LaunchDarkly/LaunchDarkly/LDClientIdentifyHook.swift index 190f3a86..8a041605 100644 --- a/LaunchDarkly/LaunchDarkly/LDClientIdentifyHook.swift +++ b/LaunchDarkly/LaunchDarkly/LDClientIdentifyHook.swift @@ -6,7 +6,7 @@ extension LDClient { let seriesData: [IdentifySeriesData] let hooksSnapshot: [Hook] } - + private func executeWithIdentifyHooks(context: LDContext, work: @escaping ((@escaping (IdentifyResult) -> Void)) -> Void) { let state = executeBeforeIdentifyHooks(context: context) work() { [weak self] result in @@ -16,12 +16,12 @@ extension LDClient { self?.executeAfterIdentifyHooks(state: state, result: result) } } - + private func executeBeforeIdentifyHooks(context: LDContext) -> IdentifyHookState? { guard !hooks.isEmpty else { return nil } - + let hooksSnapshot = self.hooks let seriesContext = IdentifySeriesContext(context: context, methodName: "identify") let seriesData = hooksSnapshot.map { hook in @@ -29,18 +29,19 @@ extension LDClient { } return IdentifyHookState(seriesContext: seriesContext, seriesData: seriesData, hooksSnapshot: hooksSnapshot) } - + private func executeAfterIdentifyHooks(state: IdentifyHookState, result: IdentifyResult) { guard !state.hooksSnapshot.isEmpty else { return } - + // Invoke hooks in reverse order and give them back the series data they gave us. zip(state.hooksSnapshot, state.seriesData).reversed().forEach { (hook, data) in _ = hook.afterIdentify(seriesContext: state.seriesContext, seriesData: data, result: result) } } - + + // swiftlint:disable:next identifier_name func _identifyHooked(context: LDContext, sheddable: Bool, useCache: IdentifyCacheUsage, timeout: TimeInterval, completion: @escaping (_ result: IdentifyResult) -> Void) { if timeout > 0 { executeWithIdentifyHooks(context: context) { hooksCompletion in diff --git a/LaunchDarkly/LaunchDarkly/Models/Hooks/Hook.swift b/LaunchDarkly/LaunchDarkly/Models/Hooks/Hook.swift index 036a22d4..3a0728db 100644 --- a/LaunchDarkly/LaunchDarkly/Models/Hooks/Hook.swift +++ b/LaunchDarkly/LaunchDarkly/Models/Hooks/Hook.swift @@ -4,13 +4,16 @@ import Foundation /// /// Hook implementations can use this to store data needed between stages. public typealias EvaluationSeriesData = [String: Any] +/// Implementation specific hook data for identify stages. +/// +/// Hook implementations can use this to store data needed between stages. public typealias IdentifySeriesData = [String: Any] /// Protocol for extending SDK functionality via hooks. public protocol Hook { /// Get metadata about the hook implementation. func metadata() -> Metadata - + /// Executed by the SDK at the start of the evaluation of a feature flag. /// /// This is not executed as part of a call to `LDClient.allFlags()`. @@ -25,7 +28,7 @@ public protocol Hook { /// `beforeEvaluation` is the first stage in this series, so this will be an empty dictionary. /// - Returns: A dictionary containing custom data that will be carried through to the next stage of the series. func beforeEvaluation(seriesContext: EvaluationSeriesContext, seriesData: EvaluationSeriesData) -> EvaluationSeriesData - + /// Executed by the SDK after the evaluation of a feature flag completes. /// /// This is not executed as part of a call to `LDClient.allFlags()`. @@ -39,7 +42,7 @@ public protocol Hook { /// - evaluationDetail: The result of the evaluation that took place before this hook was invoked. /// - Returns: A dictionary containing custom data that will be carried through to the next stage of the series (if added in the future). func afterEvaluation(seriesContext: EvaluationSeriesContext, seriesData: EvaluationSeriesData, evaluationDetail: LDEvaluationDetail) -> EvaluationSeriesData - + /// To provide custom data to the series which will be given back to your Hook at the next stage of the series, /// return a dictionary containing the custom data. You should initialize this dictionary from the `seriesData`. /// @@ -48,7 +51,7 @@ public protocol Hook { /// - seriesData: A record associated with each stage of hook invocations. Each stage is called with the data of the previous stage for a series. The input record should not be modified. /// - Returns: A dictionary containing custom data that will be carried through to the next stage of the series. func beforeIdentify(seriesContext: IdentifySeriesContext, seriesData: IdentifySeriesData) -> IdentifySeriesData - + /// Called during the execution of the identify process, after the operation completes. /// /// This is currently the last stage of the identify series in the Hook, but that may not be the case in the future. diff --git a/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift b/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift index a858adbc..5b7872e1 100644 --- a/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift +++ b/LaunchDarkly/LaunchDarkly/Networking/DarklyService.swift @@ -3,6 +3,7 @@ import LDSwiftEventSource import OSLog import DataCompression +// swiftlint:disable:next large_tuple typealias ServiceResponse = (data: Data?, urlResponse: URLResponse?, error: Error?, etag: String?) typealias ServiceCompletionHandler = (ServiceResponse) -> Void diff --git a/LaunchDarkly/LaunchDarkly/ServiceObjects/TimeoutExecutor.swift b/LaunchDarkly/LaunchDarkly/ServiceObjects/TimeoutExecutor.swift index 8b81f908..e75ee200 100644 --- a/LaunchDarkly/LaunchDarkly/ServiceObjects/TimeoutExecutor.swift +++ b/LaunchDarkly/LaunchDarkly/ServiceObjects/TimeoutExecutor.swift @@ -39,7 +39,7 @@ final class TimeoutExecutor { } return } - + let lockQueue = DispatchQueue(label: "launchdarkly.timeout.executor.lock") var finished = false @@ -52,7 +52,7 @@ final class TimeoutExecutor { shouldCall = true } } - + if shouldCall { queue.async { completion(value) } } @@ -67,7 +67,7 @@ final class TimeoutExecutor { shouldCall = true } } - + if shouldCall { completion(timeoutValue()) } diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientEvaluationHookSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientEvaluationHookSpec.swift index 633e95ca..6434a28d 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientEvaluationHookSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientEvaluationHookSpec.swift @@ -117,7 +117,9 @@ final class LDClientEvaluationHookSpec: XCTestCase { return self.before(seriesContext, seriesData) } - func afterEvaluation(seriesContext: LaunchDarkly.EvaluationSeriesContext, seriesData: LaunchDarkly.EvaluationSeriesData, evaluationDetail: LaunchDarkly.LDEvaluationDetail) -> LaunchDarkly.EvaluationSeriesData { + func afterEvaluation(seriesContext: LaunchDarkly.EvaluationSeriesContext, + seriesData: LaunchDarkly.EvaluationSeriesData, + evaluationDetail: LaunchDarkly.LDEvaluationDetail) -> LaunchDarkly.EvaluationSeriesData { return self.after(seriesContext, seriesData, evaluationDetail) } } diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientIdentifyHookSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientIdentifyHookSpec.swift index b5f93c9a..73c6d365 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientIdentifyHookSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientIdentifyHookSpec.swift @@ -20,7 +20,7 @@ final class LDClientIdentifyHookSpec: XCTestCase { testContext.subject.identify(context: LDContext.stub()) { _ in } expect(count).toEventually(equal(3)) } - + func testRegistrationWithTimeout() { var count = 0 let hook = MockHook(before: { _, data in count += 1; return data }, after: { _, data, _ in count += 2; return data }) diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientPluginsSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientPluginsSpec.swift index 08bfca00..0f072f8b 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientPluginsSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientPluginsSpec.swift @@ -133,7 +133,9 @@ final class LDClientPluginsSpec: XCTestCase { return self.before(seriesContext, seriesData) } - func afterEvaluation(seriesContext: LaunchDarkly.EvaluationSeriesContext, seriesData: LaunchDarkly.EvaluationSeriesData, evaluationDetail: LaunchDarkly.LDEvaluationDetail) -> LaunchDarkly.EvaluationSeriesData { + func afterEvaluation(seriesContext: LaunchDarkly.EvaluationSeriesContext, + seriesData: LaunchDarkly.EvaluationSeriesData, + evaluationDetail: LaunchDarkly.LDEvaluationDetail) -> LaunchDarkly.EvaluationSeriesData { return self.after(seriesContext, seriesData, evaluationDetail) } } diff --git a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift index 7590ae2e..abb04e8e 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDClientSpec.swift @@ -742,9 +742,12 @@ final class LDClientSpec: QuickSpec { expect(testContext.eventReporterMock.recordFlagEvaluationEventsCallCount) == 1 expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.flagKey) == DarklyServiceMock.FlagKeys.bool expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.value) == DarklyServiceMock.FlagValues.bool - expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.defaultValue) == .bool(DefaultFlagValues.bool) - expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.featureFlag) == testContext.flagStoreMock.storedItems.featureFlags[DarklyServiceMock.FlagKeys.bool] - expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.context) == testContext.context + expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.defaultValue) == + .bool(DefaultFlagValues.bool) + expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.featureFlag) == + testContext.flagStoreMock.storedItems.featureFlags[DarklyServiceMock.FlagKeys.bool] + expect(testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments?.context) == + testContext.context } } } @@ -781,7 +784,13 @@ final class LDClientSpec: QuickSpec { var events = [FeatureEvent]() testContext.eventReporterMock.recordFlagEvaluationEventsCallback = { let args = testContext.eventReporterMock.recordFlagEvaluationEventsReceivedArguments! - let event = FeatureEvent(key: args.flagKey, context: args.context, value: args.value, defaultValue: args.defaultValue, featureFlag: args.featureFlag, includeReason: args.includeReason, isDebug: false) + let event = FeatureEvent(key: args.flagKey, + context: args.context, + value: args.value, + defaultValue: args.defaultValue, + featureFlag: args.featureFlag, + includeReason: args.includeReason, + isDebug: false) events.append(event) } _ = testContext.subject.boolVariation(forKey: "flagA", defaultValue: DefaultFlagValues.bool) diff --git a/LaunchDarkly/LaunchDarklyTests/LDValueDecoderSpec.swift b/LaunchDarkly/LaunchDarklyTests/LDValueDecoderSpec.swift index 2a65532c..f5232846 100644 --- a/LaunchDarkly/LaunchDarklyTests/LDValueDecoderSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/LDValueDecoderSpec.swift @@ -89,17 +89,25 @@ final class LDValueDecoderSpec: XCTestCase { let container = try decoder.container(keyedBy: DynamicCodingKeys.self) guard case .some(let bio) = try container.decodeIfPresent([String: String].self, forKey: DynamicCodingKeys(string: "bio")) else { - throw DecodingError.valueNotFound([String: String].self, DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio")], debugDescription: "bio must be present and a dictionary of strings")) + throw DecodingError.valueNotFound([String: String].self, + DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio")], + debugDescription: "bio must be present and a dictionary of strings")) } guard let firstName = bio["firstName"] else { - throw DecodingError.valueNotFound(String.self, DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio"), DynamicCodingKeys(string: "firstName")], debugDescription: "bio must contain first name")) + throw DecodingError.valueNotFound(String.self, + DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio"), + DynamicCodingKeys(string: "firstName")], + debugDescription: "bio must contain first name")) } self.firstName = firstName guard let lastName = bio["lastName"] else { - throw DecodingError.valueNotFound(String.self, DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio"), DynamicCodingKeys(string: "lastName")], debugDescription: "bio must contain last name")) + throw DecodingError.valueNotFound(String.self, + DecodingError.Context(codingPath: [DynamicCodingKeys(string: "bio"), + DynamicCodingKeys(string: "lastName")], + debugDescription: "bio must contain last name")) } self.lastName = lastName diff --git a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift index d954e4fd..26686f28 100644 --- a/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift +++ b/LaunchDarkly/LaunchDarklyTests/Mocks/ClientServiceMockFactory.swift @@ -91,7 +91,13 @@ final class ClientServiceMockFactory: ClientServiceCreating { handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?)? - func makeStreamingProvider(url: URL, httpHeaders: [String: String], connectMethod: String, connectBody: Data?, handler: EventHandler, delegate: RequestHeaderTransform?, errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider { + func makeStreamingProvider(url: URL, + httpHeaders: [String: String], + connectMethod: String, + connectBody: Data?, + handler: EventHandler, + delegate: RequestHeaderTransform?, + errorHandler: ConnectionErrorHandler?) -> DarklyStreamingProvider { makeStreamingProviderCallCount += 1 makeStreamingProviderReceivedArguments = (url, httpHeaders, connectMethod, connectBody, handler, delegate, errorHandler) return DarklyStreamingProviderMock() diff --git a/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift b/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift index c0130b90..dd752de2 100644 --- a/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Networking/DarklyServiceSpec.swift @@ -112,7 +112,9 @@ final class DarklyServiceSpec: QuickSpec { expect(urlRequest?.url?.host) == testContext.config.baseUrl.host if let path = urlRequest?.url?.path { expect(path.hasPrefix("/\(DarklyService.FlagRequestPath.get)")).to(beTrue()) - let expectedContext = encodeToLDValue(testContext.context, userInfo: [LDContext.UserInfoKeys.includePrivateAttributes: true, LDContext.UserInfoKeys.redactAttributes: false]) + let expectedContext = encodeToLDValue(testContext.context, + userInfo: [LDContext.UserInfoKeys.includePrivateAttributes: true, + LDContext.UserInfoKeys.redactAttributes: false]) expect(urlRequest?.url?.lastPathComponent.jsonValue) == expectedContext } else { fail("request path is missing") @@ -165,7 +167,9 @@ final class DarklyServiceSpec: QuickSpec { expect(urlRequest?.url?.host) == testContext.config.baseUrl.host if let path = urlRequest?.url?.path { expect(path.hasPrefix("/\(DarklyService.FlagRequestPath.get)")).to(beTrue()) - let expectedContext = encodeToLDValue(testContext.context, userInfo: [LDContext.UserInfoKeys.includePrivateAttributes: true, LDContext.UserInfoKeys.redactAttributes: false]) + let expectedContext = encodeToLDValue(testContext.context, + userInfo: [LDContext.UserInfoKeys.includePrivateAttributes: true, + LDContext.UserInfoKeys.redactAttributes: false]) expect(urlRequest?.url?.lastPathComponent.jsonValue) == expectedContext } else { fail("request path is missing") @@ -604,11 +608,17 @@ final class DarklyServiceSpec: QuickSpec { context("failure") { var responses: ServiceResponses! beforeEach { + // Set up stub before waitUntil to ensure it's registered before the request is made. + // On macOS 15/Xcode 16, OHHTTPStubs error responses may not immediately trigger + // the completion handler, so we ensure the stub is ready first. + testContext.serviceMock.stubEventRequest(success: false) { eventRequest = $0 } waitUntil { done in - testContext.serviceMock.stubEventRequest(success: false) { eventRequest = $0 } testContext.service.publishEventData(testData, UUID().uuidString) { response in responses = (response.data, response.urlResponse, response.error) - done() + // Ensure done() is called on the main queue, as waitUntil may require it + DispatchQueue.main.async { + done() + } } } } @@ -665,7 +675,12 @@ final class DarklyServiceSpec: QuickSpec { } private func stubDiagnostic() -> DiagnosticStats { - DiagnosticStats(id: DiagnosticId(diagnosticId: "test-id", sdkKey: LDConfig.Constants.mockMobileKey), creationDate: 1000, dataSinceDate: 100, droppedEvents: 0, eventsInLastBatch: 0, streamInits: []) + DiagnosticStats(id: DiagnosticId(diagnosticId: "test-id", sdkKey: LDConfig.Constants.mockMobileKey), + creationDate: 1000, + dataSinceDate: 100, + droppedEvents: 0, + eventsInLastBatch: 0, + streamInits: []) } private func publishDiagnosticSpec() { @@ -712,11 +727,17 @@ final class DarklyServiceSpec: QuickSpec { context("failure") { var responses: ServiceResponses! beforeEach { + // Set up stub before waitUntil to ensure it's registered before the request is made. + // On macOS 15/Xcode 16, OHHTTPStubs error responses may not immediately trigger + // the completion handler, so we ensure the stub is ready first. + testContext.serviceMock.stubEventRequest(success: false) { diagnosticRequest = $0 } waitUntil { done in - testContext.serviceMock.stubEventRequest(success: false) { diagnosticRequest = $0 } testContext.service.publishDiagnostic(diagnosticEvent: self.stubDiagnostic()) { response in responses = (response.data, response.urlResponse, response.error) - done() + // Ensure done() is called on the main queue, as waitUntil may require it + DispatchQueue.main.async { + done() + } } } } diff --git a/LaunchDarkly/LaunchDarklyTests/Networking/HTTPHeadersSpec.swift b/LaunchDarkly/LaunchDarklyTests/Networking/HTTPHeadersSpec.swift index 4d93f26c..f3628ddc 100644 --- a/LaunchDarkly/LaunchDarklyTests/Networking/HTTPHeadersSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/Networking/HTTPHeadersSpec.swift @@ -111,7 +111,9 @@ final class HTTPHeadersSpec: XCTestCase { httpHeaders.eventRequestHeaders, httpHeaders.diagnosticRequestHeaders] allRequestTypes.forEach { headers in - XCTAssertEqual(headers["X-LaunchDarkly-Tags"], Optional("application-id/example-id application-name/example-name application-version/example-version application-version-name/example-version-name")) + XCTAssertEqual(headers["X-LaunchDarkly-Tags"], + Optional("application-id/example-id application-name/example-name " + + "application-version/example-version application-version-name/example-version-name")) } } } diff --git a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagSynchronizerSpec.swift b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagSynchronizerSpec.swift index 96669fed..373e5b2a 100644 --- a/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagSynchronizerSpec.swift +++ b/LaunchDarkly/LaunchDarklyTests/ServiceObjects/FlagSynchronizerSpec.swift @@ -996,7 +996,10 @@ final class FlagSynchronizerSpec: QuickSpec { } describe("makeFlagRequest") { var testContext: TestContext! - // This test completes the test suite on makeFlagRequest by validating the method bails out if it's called and the synchronizer is offline. While that shouldn't happen, there are 2 code paths that don't directly verify the SDK is online before calling the method, so it seems a wise precaution to validate that the method does bailout. Other tests exercise the rest of the method. + // This test completes the test suite on makeFlagRequest by validating the method bails out if it's called + // and the synchronizer is offline. While that shouldn't happen, there are 2 code paths that don't directly + // verify the SDK is online before calling the method, so it seems a wise precaution to validate that the + // method does bailout. Other tests exercise the rest of the method. context("offline") { var synchronizingError: SynchronizingError? it("does not request flags and calls onSyncComplete with an isOffline error") { diff --git a/Mintfile b/Mintfile index 8414be05..64a43d62 100644 --- a/Mintfile +++ b/Mintfile @@ -1,2 +1,2 @@ -realm/SwiftLint@0.43.1 -krzysztofzablocki/Sourcery@1.2.1 +realm/SwiftLint@0.63.0 +krzysztofzablocki/Sourcery@2.3.0 diff --git a/README.md b/README.md index 076d977a..f47053a9 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ LaunchDarkly SDK for iOS ======================== -[![Run CI](https://github.com/launchdarkly/ios-client-sdk/actions/workflows/ci.yml/badge.svg?branch=v9)](https://github.com/launchdarkly/ios-client-sdk/actions/workflows/ci.yml) +[![Run CI](https://github.com/launchdarkly/ios-client-sdk/actions/workflows/ci.yml/badge.svg?branch=v10)](https://github.com/launchdarkly/ios-client-sdk/actions/workflows/ci.yml) [![SwiftPM compatible](https://img.shields.io/badge/SwiftPM-compatible-4BC51D.svg?style=flat)](https://swift.org/package-manager/) [![CocoaPods compatible](https://img.shields.io/cocoapods/v/LaunchDarkly.svg)](https://cocoapods.org/pods/LaunchDarkly) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)