From 5dc6475d2cd4b3922b88b4f0ec6a0e37a63b8c5e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Nov 2024 14:56:40 -0300 Subject: [PATCH 01/10] Workflows refactor --- .github/workflows/base_build.yaml | 39 +++++++++++ .../{test_ios_ut_3.yaml => base_ut.yaml} | 31 ++++----- .github/workflows/build_macos.yaml | 28 ++------ .github/workflows/build_tvos.yaml | 28 ++------ .github/workflows/build_watchos.yaml | 28 ++------ .github/workflows/test_all.yaml | 41 ++++++++++++ .github/workflows/test_ios_integration.yaml | 39 ----------- .github/workflows/test_ios_integration_1.yaml | 39 ----------- .github/workflows/test_ios_streaming.yaml | 39 ----------- .github/workflows/test_ios_streaming_1.yaml | 39 ----------- .github/workflows/test_ios_streaming_2.yaml | 39 ----------- .github/workflows/test_ios_ut.yaml | 65 ------------------- .github/workflows/test_ios_ut_1.yaml | 65 ------------------- .github/workflows/test_ios_ut_2.yaml | 65 ------------------- .github/workflows/test_ios_ut_4.yaml | 65 ------------------- .github/workflows/test_semver.yaml | 39 ----------- .github/workflows/test_ut_push_manager.yaml | 65 ------------------- .github/workflows/test_ut_streaming.yaml | 65 ------------------- CHANGES.txt | 3 - SplitiOSUnit_4.xctestplan | 25 +++++++ 20 files changed, 133 insertions(+), 714 deletions(-) create mode 100644 .github/workflows/base_build.yaml rename .github/workflows/{test_ios_ut_3.yaml => base_ut.yaml} (76%) create mode 100644 .github/workflows/test_all.yaml delete mode 100644 .github/workflows/test_ios_integration.yaml delete mode 100644 .github/workflows/test_ios_integration_1.yaml delete mode 100644 .github/workflows/test_ios_streaming.yaml delete mode 100644 .github/workflows/test_ios_streaming_1.yaml delete mode 100644 .github/workflows/test_ios_streaming_2.yaml delete mode 100644 .github/workflows/test_ios_ut.yaml delete mode 100644 .github/workflows/test_ios_ut_1.yaml delete mode 100644 .github/workflows/test_ios_ut_2.yaml delete mode 100644 .github/workflows/test_ios_ut_4.yaml delete mode 100644 .github/workflows/test_semver.yaml delete mode 100644 .github/workflows/test_ut_push_manager.yaml delete mode 100644 .github/workflows/test_ut_streaming.yaml diff --git a/.github/workflows/base_build.yaml b/.github/workflows/base_build.yaml new file mode 100644 index 00000000..3ac7a9da --- /dev/null +++ b/.github/workflows/base_build.yaml @@ -0,0 +1,39 @@ +name: Build + +on: + workflow_call: + inputs: + destination: + description: Destination + required: true + type: string + scheme: + description: Scheme to use + required: true + type: string + +jobs: + build: + runs-on: [macos-14] + + steps: + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: 15.4.0 + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build + uses: sersoft-gmbh/xcodebuild-action@v3 + with: + action: build + build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=NO + configuration: Debug + derived-data-path: "${{ github.workspace }}/SplitApp" + destination: ${{ inputs.destination }} + project: Split.xcodeproj + scheme: ${{ inputs.scheme }} diff --git a/.github/workflows/test_ios_ut_3.yaml b/.github/workflows/base_ut.yaml similarity index 76% rename from .github/workflows/test_ios_ut_3.yaml rename to .github/workflows/base_ut.yaml index d57ded99..460ea296 100644 --- a/.github/workflows/test_ios_ut_3.yaml +++ b/.github/workflows/base_ut.yaml @@ -1,16 +1,18 @@ -name: Build and Test iOS UT (3) +name: Build and Test iOS UT on: - push: - branches: - - master - pull_request: - branches: - - master - - development + workflow_call: + inputs: + test-plan: + description: The test plan to run + required: true + type: string + destination: + required: true + type: string jobs: - build: + test: runs-on: [macos-14] steps: @@ -20,23 +22,22 @@ jobs: xcode-version: 15.4.0 - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build iOS and Test uses: sersoft-gmbh/xcodebuild-action@v3 with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES + action: test + build-settings: ONLY_ACTIVE_ARCH=NO configuration: Debug derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' + destination: ${{ inputs.destination }} project: Split.xcodeproj scheme: Split sdk: 'iphonesimulator' - test-plan: 'SplitiOSUnit_3' - use-xcpretty: true + test-plan: ${{ inputs.test-plan }} # - name: Install java 11 # uses: actions/setup-java@v3 diff --git a/.github/workflows/build_macos.yaml b/.github/workflows/build_macos.yaml index f6fed85b..91b3c126 100644 --- a/.github/workflows/build_macos.yaml +++ b/.github/workflows/build_macos.yaml @@ -11,27 +11,7 @@ on: jobs: build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.1.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build MacOS - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=NO - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=macOS,arch=x86_64' - project: Split.xcodeproj - scheme: Split - use-xcpretty: true + uses: ./.github/workflows/base_build.yaml + with: + destination: 'platform=macOS,arch=x86_64' + scheme: Split \ No newline at end of file diff --git a/.github/workflows/build_tvos.yaml b/.github/workflows/build_tvos.yaml index 58d6476d..18dd07a2 100644 --- a/.github/workflows/build_tvos.yaml +++ b/.github/workflows/build_tvos.yaml @@ -11,27 +11,7 @@ on: jobs: build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build tvOS - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=NO - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'generic/platform=tvOS' - project: Split.xcodeproj - scheme: Split - use-xcpretty: true \ No newline at end of file + uses: ./.github/workflows/base_build.yaml + with: + destination: 'generic/platform=tvOS' + scheme: Split \ No newline at end of file diff --git a/.github/workflows/build_watchos.yaml b/.github/workflows/build_watchos.yaml index b397d3d1..b035410c 100644 --- a/.github/workflows/build_watchos.yaml +++ b/.github/workflows/build_watchos.yaml @@ -11,27 +11,7 @@ on: jobs: build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build watchOS - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=NO - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'generic/platform=watchOS' - project: Split.xcodeproj - scheme: WatchOS - use-xcpretty: true \ No newline at end of file + uses: ./.github/workflows/base_build.yaml + with: + destination: 'generic/platform=watchOS' + scheme: WatchOS \ No newline at end of file diff --git a/.github/workflows/test_all.yaml b/.github/workflows/test_all.yaml new file mode 100644 index 00000000..a57f444d --- /dev/null +++ b/.github/workflows/test_all.yaml @@ -0,0 +1,41 @@ +name: Build and Test iOS + +on: + push: + branches: + - master + pull_request: + branches: + - master + - development + +jobs: + build: + uses: ./.github/workflows/base_build.yaml + with: + destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' + scheme: Split + test: + needs: build + strategy: + matrix: + plan: [ + SplitiOSIntegration, + SplitiOSIntegration_1, + SplitiOSStreaming, + SplitiOSStreaming_1, + SplitiOSStreaming_2, + SplitiOSUnit, + SplitiOSUnit_1, + SplitiOSUnit_2, + SplitiOSUnit_3, + SplitiOSUnit_4, + SemVer, + SplitPushManagerUT, + SplitStreamingUT + ] + fail-fast: false + uses: ./.github/workflows/base_ut.yaml + with: + test-plan: ${{ matrix.plan }} + destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' diff --git a/.github/workflows/test_ios_integration.yaml b/.github/workflows/test_ios_integration.yaml deleted file mode 100644 index a3f66336..00000000 --- a/.github/workflows/test_ios_integration.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and iOS Integration Tests - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Test iOS integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSIntegration' - use-xcpretty: true diff --git a/.github/workflows/test_ios_integration_1.yaml b/.github/workflows/test_ios_integration_1.yaml deleted file mode 100644 index 2f884b44..00000000 --- a/.github/workflows/test_ios_integration_1.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and iOS Integration Tests - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Test iOS integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSIntegration_1' - use-xcpretty: true diff --git a/.github/workflows/test_ios_streaming.yaml b/.github/workflows/test_ios_streaming.yaml deleted file mode 100644 index 8258c541..00000000 --- a/.github/workflows/test_ios_streaming.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and Test iOS Streaming - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Test iOS Streaming integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSStreaming' - use-xcpretty: true diff --git a/.github/workflows/test_ios_streaming_1.yaml b/.github/workflows/test_ios_streaming_1.yaml deleted file mode 100644 index 1f50d477..00000000 --- a/.github/workflows/test_ios_streaming_1.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and Test iOS Streaming 1 - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Test iOS Streaming integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSStreaming_1' - use-xcpretty: true diff --git a/.github/workflows/test_ios_streaming_2.yaml b/.github/workflows/test_ios_streaming_2.yaml deleted file mode 100644 index f4aaafb4..00000000 --- a/.github/workflows/test_ios_streaming_2.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and Test iOS Streaming 1 - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Test iOS Streaming integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSStreaming_2' - use-xcpretty: true diff --git a/.github/workflows/test_ios_ut.yaml b/.github/workflows/test_ios_ut.yaml deleted file mode 100644 index 3e1718d4..00000000 --- a/.github/workflows/test_ios_ut.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test iOS UT - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.1.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build iOS and Test - uses: sersoft-gmbh/xcodebuild-action@v1 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSUnit' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/.github/workflows/test_ios_ut_1.yaml b/.github/workflows/test_ios_ut_1.yaml deleted file mode 100644 index 7efc4e05..00000000 --- a/.github/workflows/test_ios_ut_1.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test iOS UT (1) - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.1.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build iOS and Test - uses: sersoft-gmbh/xcodebuild-action@v1 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSUnit_1' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/.github/workflows/test_ios_ut_2.yaml b/.github/workflows/test_ios_ut_2.yaml deleted file mode 100644 index 1412c4e6..00000000 --- a/.github/workflows/test_ios_ut_2.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test iOS UT (2) - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.1.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build iOS and Test - uses: sersoft-gmbh/xcodebuild-action@v1 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSUnit_2' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/.github/workflows/test_ios_ut_4.yaml b/.github/workflows/test_ios_ut_4.yaml deleted file mode 100644 index 6d83c355..00000000 --- a/.github/workflows/test_ios_ut_4.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test iOS UT (1) - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-latest] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.1.0 - - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Build iOS and Test 4 - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitiOSUnit_4' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/.github/workflows/test_semver.yaml b/.github/workflows/test_semver.yaml deleted file mode 100644 index 0dca8ea4..00000000 --- a/.github/workflows/test_semver.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build and test SemVer - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - - name: Test iOS integration - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SemVer' - use-xcpretty: true diff --git a/.github/workflows/test_ut_push_manager.yaml b/.github/workflows/test_ut_push_manager.yaml deleted file mode 100644 index 3fa141c7..00000000 --- a/.github/workflows/test_ut_push_manager.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test Push notification manager UT - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build iOS and Test Push notification manager - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitPushManagerUT' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/.github/workflows/test_ut_streaming.yaml b/.github/workflows/test_ut_streaming.yaml deleted file mode 100644 index a18afa6a..00000000 --- a/.github/workflows/test_ut_streaming.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Build and Test streaming UT - -on: - push: - branches: - - master - pull_request: - branches: - - master - - development - -jobs: - build: - runs-on: [macos-14] - - steps: - - name: Select Xcode - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: 15.4.0 - - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Build iOS and Test Streaming UT - uses: sersoft-gmbh/xcodebuild-action@v3 - with: - action: build test - build-settings: ONLY_ACTIVE_ARCH=NO TEST_AFTER_BUILD=YES - configuration: Debug - derived-data-path: "${{github.workspace}}/SplitApp" - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - project: Split.xcodeproj - scheme: Split - sdk: 'iphonesimulator' - test-plan: 'SplitStreamingUT' - use-xcpretty: true - - # - name: Install java 11 - # uses: actions/setup-java@v3 - # with: - # distribution: 'oracle' - # java-version: '17' - - # - name: SonarQube Install - # uses: mathrix-education/sonar-scanner@master - # env: - # ACTIONS_ALLOW_UNSECURE_COMMANDS: true - # with: - # version: 4.8.0.2856 - # scan: false - - # - name: SonarQube Scan - # run: > - # sonar-scanner --debug - # -Dsonar.login=${{ secrets.SONARQUBE_TOKEN }} - # -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - # -Dsonar.projectName=${{ github.event.repository.name }} - # -Dsonar.projectKey=splitio_ios-client - # -Dsonar.github.token=${{ secrets.GITHUB_TOKEN }} - # -Dsonar.c.file.suffixes=- - # -Dsonar.cpp.file.suffixes=- - # -Dsonar.objc.file.suffixes=- diff --git a/CHANGES.txt b/CHANGES.txt index a570fb7d..8f01699b 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -2,9 +2,6 @@ - Added support for targeting rules based on large segments. - BREAKING: Dropped support for Split Proxy below version 5.9.0. The SDK now requires Split Proxy 5.9.0 or above. -2.27.0: (Sep 30, 2024) -- Added support for large segments - 2.26.1: (Jul 30, 2024) - Added wildcard host filtering for certificate pinning. diff --git a/SplitiOSUnit_4.xctestplan b/SplitiOSUnit_4.xctestplan index 1f9a5b21..fe28c302 100644 --- a/SplitiOSUnit_4.xctestplan +++ b/SplitiOSUnit_4.xctestplan @@ -120,6 +120,12 @@ "MultiClientStreamingResetTest", "Murmur3HashingTest", "MySegmentServerErrorTest", + "MySegmentUpdateTest", + "MySegmentUpdateTest\/testMyLargeSegmentsUpdate()", + "MySegmentUpdateTest\/testMySegmentsLargeUpdateBounded()", + "MySegmentUpdateTest\/testMySegmentsUpdate()", + "MySegmentUpdateTest\/testMySegmentsUpdateBounded()", + "MySegmentUpdateTest\/testSeveralNotificationAndOneFetch()", "MySegmentUpdateV2Test", "MySegmentUpdatedTest", "MySegmentsBgSyncWorkerTest", @@ -150,6 +156,25 @@ "RecorderFlusherCheckerTests", "RegexTest", "SdkUpdateStreamingTest", + "SegmentsSyncHelperTests", + "SegmentsSyncHelperTests\/testCdnByPassNoTillChange()", + "SegmentsSyncHelperTests\/testCdnByPassNoTillNoChange()", + "SegmentsSyncHelperTests\/testCdnByPassTill()", + "SegmentsSyncHelperTests\/testDidffGoallCnMs()", + "SegmentsSyncHelperTests\/testDiffGoalCnMls()", + "SegmentsUpdateWorkerTests", + "SegmentsUpdateWorkerTests\/testLargeSegmentsKeyListAdd()", + "SegmentsUpdateWorkerTests\/testLargeSegmentsKeyListNoAction()", + "SegmentsUpdateWorkerTests\/testLargeSegmentsKeyListRemoval()", + "SegmentsUpdateWorkerTests\/testMyLargeSegmentsNonRemoval()", + "SegmentsUpdateWorkerTests\/testMyLargeSegmentsRemoval()", + "SegmentsUpdateWorkerTests\/testMySegmentsNonRemoval()", + "SegmentsUpdateWorkerTests\/testMySegmentsRemoval()", + "SegmentsUpdateWorkerTests\/testSegmentsKeyListAdd()", + "SegmentsUpdateWorkerTests\/testSegmentsKeyListNoAction()", + "SegmentsUpdateWorkerTests\/testSegmentsKeyListRemove()", + "SegmentsUpdateWorkerTests\/testUnbounded()", + "SegmentsUpdateWorkerTests\/testUnboundedLarge()", "SemverIntegrationTest", "SemverTest", "ServiceEndpointsTests", From 2f81a1cb19fd364b185eb3e9bd97244e9a9d15a1 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 7 Nov 2024 17:21:24 -0300 Subject: [PATCH 02/10] Remove previous job --- .github/workflows/test_all.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/test_all.yaml b/.github/workflows/test_all.yaml index a57f444d..4b60d55d 100644 --- a/.github/workflows/test_all.yaml +++ b/.github/workflows/test_all.yaml @@ -10,13 +10,7 @@ on: - development jobs: - build: - uses: ./.github/workflows/base_build.yaml - with: - destination: 'platform=iOS Simulator,OS=17.2,name=iPhone 15' - scheme: Split test: - needs: build strategy: matrix: plan: [ From 0430fce96c327f3d84a7964abee35d567f88d66b Mon Sep 17 00:00:00 2001 From: gthea Date: Wed, 18 Dec 2024 15:06:36 -0300 Subject: [PATCH 03/10] Add trackImpressions property to DTO (#598) --- Split/Api/DefaultSplitManager.swift | 1 + Split/Api/SplitView.swift | 1 + Split/Engine/DefaultTreatmentManager.swift | 7 +++-- Split/Engine/Evaluator.swift | 26 ++++++++++++----- Split/Models/SplitModel/Split.swift | 5 +++- Split/Network/Sync/ImpressionsTracker.swift | 2 +- SplitTests/EvaluatorTests.swift | 31 +++++++++++++++++++++ SplitTests/Resources/splits.json | 1 + SplitTests/SplitManagerTest.swift | 12 ++++---- 9 files changed, 69 insertions(+), 17 deletions(-) diff --git a/Split/Api/DefaultSplitManager.swift b/Split/Api/DefaultSplitManager.swift index 8ac233df..be8efc07 100644 --- a/Split/Api/DefaultSplitManager.swift +++ b/Split/Api/DefaultSplitManager.swift @@ -43,6 +43,7 @@ import Foundation splitView.killed = split.killed splitView.sets = Array(split.sets ?? []) splitView.configs = split.configurations ?? [String: String]() + splitView.trackImpressions = split.trackImpressions ?? true if let conditions = split.conditions { var treatments = Set() diff --git a/Split/Api/SplitView.swift b/Split/Api/SplitView.swift index 150f9018..bf828a5d 100644 --- a/Split/Api/SplitView.swift +++ b/Split/Api/SplitView.swift @@ -26,4 +26,5 @@ public class SplitView: NSObject, Codable { } @objc public var configs: [String: String]? + @objc public var trackImpressions: Bool = true } diff --git a/Split/Engine/DefaultTreatmentManager.swift b/Split/Engine/DefaultTreatmentManager.swift index 6b224f31..1326289b 100644 --- a/Split/Engine/DefaultTreatmentManager.swift +++ b/Split/Engine/DefaultTreatmentManager.swift @@ -234,11 +234,12 @@ extension DefaultTreatmentManager { attributes: mergedAttributes, validationTag: validationTag) logImpression(label: result.label, changeNumber: result.changeNumber, - treatment: result.treatment, splitName: trimmedSplitName, attributes: mergedAttributes) + treatment: result.treatment, splitName: trimmedSplitName, attributes: mergedAttributes, + trackImpressions: result.trackImpressions) return SplitResult(treatment: result.treatment, config: result.configuration) } catch { logImpression(label: ImpressionsConstants.exception, treatment: SplitConstants.control, - splitName: trimmedSplitName, attributes: mergedAttributes) + splitName: trimmedSplitName, attributes: mergedAttributes, trackImpressions: true) return SplitResult(treatment: SplitConstants.control) } } @@ -263,7 +264,7 @@ extension DefaultTreatmentManager { } private func logImpression(label: String, changeNumber: Int64? = nil, - treatment: String, splitName: String, attributes: [String: Any]? = nil) { + treatment: String, splitName: String, attributes: [String: Any]? = nil, trackImpressions: Bool) { let keyImpression = KeyImpression(featureName: splitName, keyName: key.matchingKey, diff --git a/Split/Engine/Evaluator.swift b/Split/Engine/Evaluator.swift index 924fce5c..867da07f 100644 --- a/Split/Engine/Evaluator.swift +++ b/Split/Engine/Evaluator.swift @@ -12,12 +12,14 @@ struct EvaluationResult { var label: String var changeNumber: Int64? var configuration: String? + var trackImpressions: Bool - init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil) { + init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil, trackImpressions: Bool = true) { self.treatment = treatment self.label = label self.changeNumber = changeNumber self.configuration = configuration + self.trackImpressions = trackImpressions } } @@ -77,7 +79,8 @@ class DefaultEvaluator: Evaluator { return EvaluationResult(treatment: defaultTreatment, label: ImpressionsConstants.killed, changeNumber: changeNumber, - configuration: split.configurations?[defaultTreatment]) + configuration: split.configurations?[defaultTreatment], + trackImpressions: split.shouldTrackImpression()) } var inRollOut: Bool = false @@ -106,7 +109,8 @@ class DefaultEvaluator: Evaluator { return EvaluationResult(treatment: defaultTreatment, label: ImpressionsConstants.notInSplit, changeNumber: changeNumber, - configuration: split.configurations?[defaultTreatment]) + configuration: split.configurations?[defaultTreatment], + trackImpressions: split.shouldTrackImpression()) } inRollOut = true } @@ -121,23 +125,25 @@ class DefaultEvaluator: Evaluator { partions: condition.partitions, algo: splitAlgo) return EvaluationResult(treatment: treatment, label: condition.label!, changeNumber: changeNumber, - configuration: split.configurations?[treatment]) + configuration: split.configurations?[treatment], + trackImpressions: split.shouldTrackImpression()) } } let result = EvaluationResult(treatment: defaultTreatment, label: ImpressionsConstants.noConditionMatched, changeNumber: changeNumber, - configuration: split.configurations?[defaultTreatment]) + configuration: split.configurations?[defaultTreatment], + trackImpressions: split.shouldTrackImpression()) return result } catch EvaluatorError.matcherNotFound { Logger.e("The matcher has not been found") return EvaluationResult(treatment: SplitConstants.control, label: ImpressionsConstants.matcherNotFound, - changeNumber: changeNumber) + changeNumber: changeNumber, trackImpressions: split.shouldTrackImpression()) } } private func getContext() -> EvalContext { - return EvalContext(evaluator: self, + return EvalContext(evaluator: self, mySegmentsStorage: mySegmentsStorage, myLargeSegmentsStorage: myLargeSegmentsStorage) } @@ -149,3 +155,9 @@ class DefaultEvaluator: Evaluator { return matchingKey } } + +private extension Split { + func shouldTrackImpression() -> Bool { + return self.trackImpressions ?? true + } +} diff --git a/Split/Models/SplitModel/Split.swift b/Split/Models/SplitModel/Split.swift index a782786f..b90b4266 100644 --- a/Split/Models/SplitModel/Split.swift +++ b/Split/Models/SplitModel/Split.swift @@ -24,12 +24,13 @@ class SplitDTO: NSObject, SplitBase, Codable { var algo: Int? var configurations: [String: String]? var sets: Set? + var trackImpressions: Bool? var json: String = "" var isParsed = true - init(name: String, trafficType: String, status: Status, sets: Set?, json: String, killed: Bool = false) { + init(name: String, trafficType: String, status: Status, sets: Set?, json: String, killed: Bool = false, trackImpressions: Bool = true) { self.name = name self.trafficTypeName = trafficType self.status = status @@ -37,6 +38,7 @@ class SplitDTO: NSObject, SplitBase, Codable { self.json = json self.killed = killed self.isParsed = false + self.trackImpressions = trackImpressions } enum CodingKeys: String, CodingKey { @@ -53,5 +55,6 @@ class SplitDTO: NSObject, SplitBase, Codable { case algo case configurations case sets + case trackImpressions } } diff --git a/Split/Network/Sync/ImpressionsTracker.swift b/Split/Network/Sync/ImpressionsTracker.swift index 56c70b15..a679c005 100644 --- a/Split/Network/Sync/ImpressionsTracker.swift +++ b/Split/Network/Sync/ImpressionsTracker.swift @@ -192,7 +192,7 @@ class DefaultImpressionsTracker: ImpressionsTracker { } private func saveUniqueKeys() { - // Just doble checking + // Just double checking if !isPersistenceEnabled { return } diff --git a/SplitTests/EvaluatorTests.swift b/SplitTests/EvaluatorTests.swift index c76b3594..b659ca08 100644 --- a/SplitTests/EvaluatorTests.swift +++ b/SplitTests/EvaluatorTests.swift @@ -371,6 +371,37 @@ class EvaluatorTests: XCTestCase { inLargeSegmentWhiteListTest(key: "the_bad_key", treatment: "off") } + func testTrackImpressionsNil() { + withTrackImpressions(nil) + } + + func testTrackImpressionsTrue() { + withTrackImpressions(true) + } + + func testTrackImpressionsFalse() { + withTrackImpressions(false) + } + + private func withTrackImpressions(_ track: Bool?) { + var result: EvaluationResult! + var evaluator: Evaluator! + guard let split = loadSplit(splitName: "split_sample_feature6") else { + XCTAssertTrue(false) + return + } + split.algo = 2 + if (track != nil) { + split.trackImpressions = track + } + evaluator = customEvaluator(split: split) + result = try? evaluator.evalTreatment(matchingKey: matchingKey, bucketingKey: nil, splitName: split.name!, attributes: nil) + + XCTAssertNotNil(result) + XCTAssertEqual("t4_6", result?.treatment) + XCTAssertEqual(track ?? true, result!.trackImpressions) + } + func inLargeSegmentWhiteListTest(key: String, treatment: String = "on") { var treatment = "" diff --git a/SplitTests/Resources/splits.json b/SplitTests/Resources/splits.json index 4221c429..3c1f1df4 100644 --- a/SplitTests/Resources/splits.json +++ b/SplitTests/Resources/splits.json @@ -10,6 +10,7 @@ "changeNumber": 1, "algo": 2, "sets": ["set1", "set2"], + "trackImpressions": false, "configurations": { "t1": "{\"f1\": \"v1\"}", "t2": "{\"f2\": \"v2\"}" diff --git a/SplitTests/SplitManagerTest.swift b/SplitTests/SplitManagerTest.swift index dfec4430..2c9ef321 100644 --- a/SplitTests/SplitManagerTest.swift +++ b/SplitTests/SplitManagerTest.swift @@ -16,7 +16,7 @@ class SplitManagerTest: XCTestCase { var loadedSplits: [Split]! var manager: SplitManager! var splitsStorage: SplitsStorageStub! - + override func setUp() { let bundle = Bundle(for: type(of: self)) let path = bundle.path(forResource: "splits", ofType: "json")! @@ -27,10 +27,10 @@ class SplitManagerTest: XCTestCase { changeNumber: 1, updateTimestamp: 100)) manager = DefaultSplitManager(splitsStorage: splitsStorage) } - + override func tearDown() { } - + func testInitialSplitLoaded() { let splits = manager.splitNames @@ -68,6 +68,7 @@ class SplitManagerTest: XCTestCase { XCTAssertEqual(split0?.trafficType, "custom", "Split0 traffic type") XCTAssertEqual(split0?.sets?.sorted(), ["set1", "set2"]) XCTAssertNotNil(split0?.configs) + XCTAssertFalse(split0?.trackImpressions ?? true, "Split0 track impressions") XCTAssertEqual(treatments0?.count, 6, "Split0 treatment count") XCTAssertEqual(treatments0?.sorted().joined(separator: ",").lowercased(), "t1_0,t2_0,t3_0,t4_0,t5_0,t6_0", "Split0 treatment names") @@ -86,8 +87,9 @@ class SplitManagerTest: XCTestCase { XCTAssertEqual(treatments1?.sorted().joined(separator: ",").lowercased(), "t1_1,t2_1,t3_1,t4_1,t5_1,t6_1", "Split1 treatment names") XCTAssertEqual([], splitWithoutSets!.sets!) + XCTAssertTrue(splitWithoutSets!.trackImpressions, "Split1 track impressions") } - + func testAddOneSplit() { let bundle = Bundle(for: type(of: self)) let path = bundle.path(forResource: "split_sample_feature6", ofType: "json")! @@ -99,7 +101,7 @@ class SplitManagerTest: XCTestCase { XCTAssertEqual(splits.count, 7, "Added one feature flag count") XCTAssertEqual(names.sorted().joined(separator: ",").lowercased(), "sample_feature0,sample_feature1,sample_feature2,sample_feature3,sample_feature4,sample_feature5,sample_feature6", "Added one feature flag name check") } - + func testEmptyName(){ let split = manager.split(featureName: " ") XCTAssertNil(split) From 0d3785fb7a5e90e93adf3ff9cd2b56fde98c55c3 Mon Sep 17 00:00:00 2001 From: gthea Date: Fri, 20 Dec 2024 10:01:14 -0300 Subject: [PATCH 04/10] Decorated impressions (#599) --- Split.xcodeproj/project.pbxproj | 6 ++ Split/Engine/DefaultTreatmentManager.swift | 6 +- Split/Engine/Evaluator.swift | 2 +- Split/Impressions/DecoratedImpression.swift | 16 +++ Split/Network/Sync/ImpressionsTracker.swift | 19 ++-- Split/Network/Sync/SyncCommons.swift | 2 +- Split/Network/Sync/Synchronizer.swift | 2 +- SplitTests/Fake/ImpressionsLoggerStub.swift | 5 +- .../Streaming/ImpressionsTrackerStub.swift | 2 +- .../Fake/Streaming/SynchronizerSpy.swift | 2 +- .../Fake/Streaming/SynchronizerStub.swift | 2 +- .../Streaming/ImpressionsTrackerTest.swift | 99 +++++++++++-------- 12 files changed, 103 insertions(+), 60 deletions(-) create mode 100644 Split/Impressions/DecoratedImpression.swift diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index be595502..071263f7 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -1107,6 +1107,8 @@ C5977C642BF7D5FC003E293A /* HttpParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5A3F5202BEBDAD6009FACD3 /* HttpParameter.swift */; }; C5A3F51F2BEBD893009FACD3 /* Spec.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5A3F51E2BEBD893009FACD3 /* Spec.swift */; }; C5A3F5212BEBDAD6009FACD3 /* HttpParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5A3F5202BEBDAD6009FACD3 /* HttpParameter.swift */; }; + C5BD1E4C2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; + C5BD1E4D2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1919,6 +1921,7 @@ C5977C582BF53F43003E293A /* splitchanges_unsupported.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = splitchanges_unsupported.json; sourceTree = ""; }; C5A3F51E2BEBD893009FACD3 /* Spec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spec.swift; sourceTree = ""; }; C5A3F5202BEBDAD6009FACD3 /* HttpParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpParameter.swift; sourceTree = ""; }; + C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecoratedImpression.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2145,6 +2148,7 @@ 3B6DEE8720EA6AE00067435E /* Impressions */ = { isa = PBXGroup; children = ( + C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */, 955E12322BFBF22800AE6D10 /* UniqueKey.swift */, 952E2679283410620015D633 /* HashedImpression.swift */, 95D9446A283BEE4D00D7FFED /* UniqueKeys.swift */, @@ -4081,6 +4085,7 @@ 5905D4CC2555F99D006DA3B1 /* ImpressionEntity.swift in Sources */, 59B8B8B0220C6E3F003E0D5A /* ValidationErrorInfo.swift in Sources */, 956A7E252979F2290080D53C /* SplitsEncoder.swift in Sources */, + C5BD1E4C2D11A993008EF198 /* DecoratedImpression.swift in Sources */, 955A5A52258D04DF00CAEE9F /* RecorderWorker.swift in Sources */, 593225FF24A4D8FD00496D8B /* HttpResponse.swift in Sources */, 952E266B2833E35A0015D633 /* PersistentUniqueKeysStorage.swift in Sources */, @@ -4596,6 +4601,7 @@ 95B02CD828D0BDC20030EC8B /* String+Utils.swift in Sources */, 95B02CD928D0BDC20030EC8B /* Array+DynamicCodable.swift in Sources */, 95B02CDA28D0BDC20030EC8B /* Dictionary+DynamicCodable.swift in Sources */, + C5BD1E4D2D11A993008EF198 /* DecoratedImpression.swift in Sources */, 95B02CDB28D0BDC20030EC8B /* Bundle+Finder.swift in Sources */, 95B02CDC28D0BDC20030EC8B /* Bundle+Name.swift in Sources */, 95B02CDD28D0BDC20030EC8B /* UInt64+bits.swift in Sources */, diff --git a/Split/Engine/DefaultTreatmentManager.swift b/Split/Engine/DefaultTreatmentManager.swift index 1326289b..2d447f9a 100644 --- a/Split/Engine/DefaultTreatmentManager.swift +++ b/Split/Engine/DefaultTreatmentManager.swift @@ -264,7 +264,8 @@ extension DefaultTreatmentManager { } private func logImpression(label: String, changeNumber: Int64? = nil, - treatment: String, splitName: String, attributes: [String: Any]? = nil, trackImpressions: Bool) { + treatment: String, splitName: String, attributes: [String: Any]? = nil, + trackImpressions: Bool) { let keyImpression = KeyImpression(featureName: splitName, keyName: key.matchingKey, @@ -273,7 +274,8 @@ extension DefaultTreatmentManager { label: (splitConfig.isLabelsEnabled ? label : nil), time: Date().unixTimestampInMiliseconds(), changeNumber: changeNumber) - impressionLogger.pushImpression(impression: keyImpression) + impressionLogger.pushImpression( + impression: DecoratedImpression(impression: keyImpression, trackImpressions: trackImpressions)) if let externalImpressionHandler = splitConfig.impressionListener { let impression = keyImpression.toImpression() diff --git a/Split/Engine/Evaluator.swift b/Split/Engine/Evaluator.swift index 867da07f..4f2655ed 100644 --- a/Split/Engine/Evaluator.swift +++ b/Split/Engine/Evaluator.swift @@ -56,7 +56,7 @@ class DefaultEvaluator: Evaluator { private let mySegmentsStorage: MySegmentsStorage private let myLargeSegmentsStorage: MySegmentsStorage? - init(splitsStorage: SplitsStorage, + init(splitsStorage: SplitsStorage, mySegmentsStorage: MySegmentsStorage, myLargeSegmentsStorage: MySegmentsStorage?) { self.splitsStorage = splitsStorage diff --git a/Split/Impressions/DecoratedImpression.swift b/Split/Impressions/DecoratedImpression.swift new file mode 100644 index 00000000..ea6a26f3 --- /dev/null +++ b/Split/Impressions/DecoratedImpression.swift @@ -0,0 +1,16 @@ +// +// DecoratedImpression.swift +// Split +// +// Copyright © 2024 Split. All rights reserved. +// + +struct DecoratedImpression { + let impression: KeyImpression + let trackImpressions: Bool + + init(impression: KeyImpression, trackImpressions: Bool) { + self.impression = impression + self.trackImpressions = trackImpressions + } +} diff --git a/Split/Network/Sync/ImpressionsTracker.swift b/Split/Network/Sync/ImpressionsTracker.swift index a679c005..67b691af 100644 --- a/Split/Network/Sync/ImpressionsTracker.swift +++ b/Split/Network/Sync/ImpressionsTracker.swift @@ -20,7 +20,7 @@ protocol ImpressionsTracker: AnyObject { func resume() func stop(_ service: RecordingService) func flush() - func push(_ impression: KeyImpression) + func push(_ decoratedImpression: DecoratedImpression) func destroy() func enableTracking(_ enable: Bool) func enablePersistence(_ enable: Bool) @@ -96,19 +96,21 @@ class DefaultImpressionsTracker: ImpressionsTracker { } } - func push(_ impression: KeyImpression) { + func push(_ decoratedImpression: DecoratedImpression) { if !isTrackingEnabled { Logger.v("Impression not tracked because tracking is disabled") return } + let impression = decoratedImpression.impression + // This should not happen guard let featureName = impression.featureName else { return } - if isNoneImpressionsMode() { + if isNoneImpressionsMode() || !decoratedImpression.trackImpressions { uniqueKeyTracker?.track(userKey: impression.keyName, featureName: featureName) impressionsCounter?.inc(featureName: featureName, timeframe: impression.time, amount: 1) if uniqueKeyFlushChecker? @@ -185,8 +187,7 @@ class DefaultImpressionsTracker: ImpressionsTracker { if !isPersistenceEnabled { return } - if isOptimizedImpressionsMode() || isNoneImpressionsMode(), - let counts = impressionsCounter?.popAll() { + if let counts = impressionsCounter?.popAll() { storageContainer.impressionsCountStorage.pushMany(counts: counts) } } @@ -196,9 +197,8 @@ class DefaultImpressionsTracker: ImpressionsTracker { if !isPersistenceEnabled { return } - if isNoneImpressionsMode() { - uniqueKeyTracker?.saveAndClear() - } + + uniqueKeyTracker?.saveAndClear() } private func saveHashedImpressions() { @@ -210,9 +210,12 @@ class DefaultImpressionsTracker: ImpressionsTracker { switch splitConfig.$impressionsMode { case .optimized: createImpressionsRecorder() + createUniqueKeysRecorder() createImpressionsCountRecorder() case .debug: createImpressionsRecorder() + createUniqueKeysRecorder() + createImpressionsCountRecorder() case .none: createUniqueKeysRecorder() createImpressionsCountRecorder() diff --git a/Split/Network/Sync/SyncCommons.swift b/Split/Network/Sync/SyncCommons.swift index 4f376692..2771c417 100644 --- a/Split/Network/Sync/SyncCommons.swift +++ b/Split/Network/Sync/SyncCommons.swift @@ -26,5 +26,5 @@ struct SplitStorageContainer { } protocol ImpressionLogger { - func pushImpression(impression: KeyImpression) + func pushImpression(impression: DecoratedImpression) } diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index 22893bc0..4a8320fc 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -192,7 +192,7 @@ class DefaultSynchronizer: Synchronizer { } } - func pushImpression(impression: KeyImpression) { + func pushImpression(impression: DecoratedImpression) { flushQueue.async { [weak self] in guard let self = self else { return } diff --git a/SplitTests/Fake/ImpressionsLoggerStub.swift b/SplitTests/Fake/ImpressionsLoggerStub.swift index caa811a0..d3ac21b9 100644 --- a/SplitTests/Fake/ImpressionsLoggerStub.swift +++ b/SplitTests/Fake/ImpressionsLoggerStub.swift @@ -12,13 +12,12 @@ import Foundation class ImpressionsLoggerStub: ImpressionLogger { var impressions = [String: KeyImpression]() var impressionsPushedCount = 0 - func pushImpression(impression: KeyImpression) { + func pushImpression(impression: DecoratedImpression) { + let impression = impression.impression guard let splitName = impression.featureName else { return } impressions[splitName] = impression impressionsPushedCount+=1 } - - } diff --git a/SplitTests/Fake/Streaming/ImpressionsTrackerStub.swift b/SplitTests/Fake/Streaming/ImpressionsTrackerStub.swift index e66ffa0b..f08e3150 100644 --- a/SplitTests/Fake/Streaming/ImpressionsTrackerStub.swift +++ b/SplitTests/Fake/Streaming/ImpressionsTrackerStub.swift @@ -46,7 +46,7 @@ class ImpressionsTrackerStub: ImpressionsTracker { } var pushCalled = false - func push(_ impression: KeyImpression) { + func push(_ impression: DecoratedImpression) { pushCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index 4cb902b9..da1b1011 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -150,7 +150,7 @@ class SynchronizerSpy: Synchronizer { splitSynchronizer.pushEvent(event: event) } - func pushImpression(impression: KeyImpression) { + func pushImpression(impression: DecoratedImpression) { pushImpressionCalled = true splitSynchronizer.pushImpression(impression: impression) } diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 4b02b925..f5bfb64a 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -175,7 +175,7 @@ class SynchronizerStub: Synchronizer { pushEventCalled = true } - func pushImpression(impression: KeyImpression) { + func pushImpression(impression: DecoratedImpression) { pushImpressionCalled = true } diff --git a/SplitTests/Streaming/ImpressionsTrackerTest.swift b/SplitTests/Streaming/ImpressionsTrackerTest.swift index 7ef41f1e..ad04628b 100644 --- a/SplitTests/Streaming/ImpressionsTrackerTest.swift +++ b/SplitTests/Streaming/ImpressionsTrackerTest.swift @@ -34,47 +34,63 @@ class ImpressionsTrackerTest: XCTestCase { createImpressionsTracker(impressionsMode: .optimized, realObserver: true) let impression = createImpression() - for _ in 0..<5 { - impressionsTracker.push(impression) + for i in 0..<5 { + impressionsTracker.push(decorate(impression, track: i % 2 == 0)) } + // Before save an clear + let trackedKeys = uniqueKeyTracker.trackedKeys + + // Calling pause to save items on storage + impressionsTracker.pause() + ThreadUtils.delay(seconds: 1) - XCTAssertEqual(1, telemetryProducer.impressions[.queued]) - XCTAssertEqual(4, telemetryProducer.impressions[.deduped]) - XCTAssertEqual(0, uniqueKeyTracker.trackedKeys.count) + XCTAssertEqual(1, telemetryProducer.impressions[.queued] ?? 0) + XCTAssertEqual(2, telemetryProducer.impressions[.deduped] ?? 0) + XCTAssertEqual(1, trackedKeys.count) XCTAssertEqual(1, impressionsStorage.impressions.count) + // 4 counts; 2 not tracked & 2 deduped + XCTAssertEqual(4, impressionsCountStorage.storedImpressions.values.filter { $0.feature == "feature" }[0].count) } func testImpressionPushDebug() { createImpressionsTracker(impressionsMode: .debug) var impression = createImpression() - for _ in 0..<5 { + for i in 0..<5 { impression.storageId = UUID().uuidString - impressionsTracker.push(impression) + impressionsTracker.push(decorate(impression, track: i % 2 == 0)) } + // Before save an clear + let trackedKeys = uniqueKeyTracker.trackedKeys + + // Calling pause to save items on storage + impressionsTracker.pause() + ThreadUtils.delay(seconds: 1) - XCTAssertEqual(5, telemetryProducer.impressions[.queued] ?? 0) + XCTAssertEqual(3, telemetryProducer.impressions[.queued] ?? 0) XCTAssertEqual(0, telemetryProducer.impressions[.deduped] ?? 0) - XCTAssertEqual(0, uniqueKeyTracker.trackedKeys.count) - XCTAssertEqual(5, impressionsStorage.impressions.count) + XCTAssertEqual(1, trackedKeys.count) + XCTAssertEqual(3, impressionsStorage.impressions.count) + // 2 not tracked + XCTAssertEqual(2, impressionsCountStorage.storedImpressions.values.filter { $0.feature == "feature" }[0].count) } func testImpressionPushNone() { createImpressionsTracker(impressionsMode: .none) - impressionsTracker.push(createImpression(keyName: "k1", featureName: "f1")) - impressionsTracker.push(createImpression(keyName: "k1", featureName: "f2")) - impressionsTracker.push(createImpression(keyName: "k1", featureName: "f3")) - impressionsTracker.push(createImpression(keyName: "k1", featureName: "f3")) - impressionsTracker.push(createImpression(keyName: "k2", featureName: "f2")) - impressionsTracker.push(createImpression(keyName: "k3", featureName: "f3")) + impressionsTracker.push(decorate(createImpression(keyName: "k1", featureName: "f1"))) + impressionsTracker.push(decorate(createImpression(keyName: "k1", featureName: "f2"))) + impressionsTracker.push(decorate(createImpression(keyName: "k1", featureName: "f3"))) + impressionsTracker.push(decorate(createImpression(keyName: "k1", featureName: "f3"))) + impressionsTracker.push(decorate(createImpression(keyName: "k2", featureName: "f2"))) + impressionsTracker.push(decorate(createImpression(keyName: "k3", featureName: "f3"))) // Before save an clear let trackedKeys = uniqueKeyTracker.trackedKeys - // Callling pause to save items on storage + // Calling pause to save items on storage impressionsTracker.pause() ThreadUtils.delay(seconds: 1) @@ -96,7 +112,7 @@ class ImpressionsTrackerTest: XCTestCase { XCTAssertTrue(periodicImpressionsRecorderWorker.startCalled) XCTAssertTrue(periodicImpressionsCountRecorderWorker.startCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.startCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.startCalled) } func testStartDebug() { @@ -104,8 +120,8 @@ class ImpressionsTrackerTest: XCTestCase { impressionsTracker.start() XCTAssertTrue(periodicImpressionsRecorderWorker.startCalled) - XCTAssertFalse(periodicImpressionsCountRecorderWorker.startCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.startCalled) + XCTAssertTrue(periodicImpressionsCountRecorderWorker.startCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.startCalled) } func testStartNone() { @@ -129,7 +145,7 @@ class ImpressionsTrackerTest: XCTestCase { pauseTest(mode: .none) } - func pauseTest(mode: ImpressionsMode) { + private func pauseTest(mode: ImpressionsMode) { createImpressionsTracker(impressionsMode: mode) let observer = impressionsObserver as! ImpressionsObserverMock @@ -141,16 +157,12 @@ class ImpressionsTrackerTest: XCTestCase { XCTAssertTrue(periodicImpressionsRecorderWorker.pauseCalled) } - if mode != .debug { - XCTAssertTrue(periodicImpressionsCountRecorderWorker.pauseCalled) - XCTAssertTrue(impressionsCountStorage.pushManyCalled) - } - - if mode == .none { - XCTAssertTrue(periodicUniqueKeysRecorderWorker.pauseCalled) - XCTAssertTrue(uniqueKeyTracker.saveAndClearCalled) - } + XCTAssertTrue(periodicImpressionsCountRecorderWorker.pauseCalled) + XCTAssertTrue(impressionsCountStorage.pushManyCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.pauseCalled) + XCTAssertTrue(uniqueKeyTracker.saveAndClearCalled) + XCTAssertTrue(observer.saveCalled) } @@ -162,7 +174,7 @@ class ImpressionsTrackerTest: XCTestCase { XCTAssertTrue(periodicImpressionsRecorderWorker.resumeCalled) XCTAssertTrue(periodicImpressionsCountRecorderWorker.resumeCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.resumeCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.resumeCalled) } func testStopOptimized() { @@ -172,17 +184,17 @@ class ImpressionsTrackerTest: XCTestCase { XCTAssertTrue(periodicImpressionsRecorderWorker.stopCalled) XCTAssertTrue(periodicImpressionsCountRecorderWorker.stopCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.startCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.startCalled) } func testStopDebug() { - createImpressionsTracker(impressionsMode: .optimized) + createImpressionsTracker(impressionsMode: .debug) impressionsTracker.start() impressionsTracker.stop(.all) XCTAssertTrue(periodicImpressionsRecorderWorker.stopCalled) XCTAssertTrue(periodicImpressionsCountRecorderWorker.stopCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.startCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.startCalled) } func testStopNone() { @@ -202,7 +214,7 @@ class ImpressionsTrackerTest: XCTestCase { XCTAssertTrue(impressionsRecorderWorker.flushCalled) XCTAssertTrue(impressionsCountRecorderWorker.flushCalled) - XCTAssertFalse(uniqueKeysRecorderWorker.flushCalled) + XCTAssertTrue(uniqueKeysRecorderWorker.flushCalled) } func testFlushDebug() { @@ -211,8 +223,8 @@ class ImpressionsTrackerTest: XCTestCase { impressionsTracker.flush() XCTAssertTrue(impressionsRecorderWorker.flushCalled) - XCTAssertFalse(impressionsCountRecorderWorker.flushCalled) - XCTAssertFalse(uniqueKeysRecorderWorker.flushCalled) + XCTAssertTrue(impressionsCountRecorderWorker.flushCalled) + XCTAssertTrue(uniqueKeysRecorderWorker.flushCalled) } func testFlushNone() { @@ -232,17 +244,19 @@ class ImpressionsTrackerTest: XCTestCase { impressionsTracker.destroy() XCTAssertTrue(periodicImpressionsRecorderWorker.destroyCalled) XCTAssertTrue(periodicImpressionsCountRecorderWorker.destroyCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.destroyCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.destroyCalled) XCTAssertTrue(observer.saveCalled) } func testDestroyDebug() { createImpressionsTracker(impressionsMode: .debug) + let observer = impressionsObserver as! ImpressionsObserverMock impressionsTracker.start() impressionsTracker.destroy() XCTAssertTrue(periodicImpressionsRecorderWorker.destroyCalled) - XCTAssertFalse(periodicImpressionsCountRecorderWorker.destroyCalled) - XCTAssertFalse(periodicUniqueKeysRecorderWorker.destroyCalled) + XCTAssertTrue(periodicImpressionsCountRecorderWorker.destroyCalled) + XCTAssertTrue(periodicUniqueKeysRecorderWorker.destroyCalled) + XCTAssertTrue(observer.saveCalled) } func testDestroyNone() { @@ -272,7 +286,7 @@ class ImpressionsTrackerTest: XCTestCase { let impression = createImpression(randomId: true) for _ in 0..<5 { - impressionsTracker.push(impression) + impressionsTracker.push(decorate(impression)) } // Before save an clear @@ -292,7 +306,10 @@ class ImpressionsTrackerTest: XCTestCase { return KeyImpression(featureName: featureName, keyName: keyName, treatment: "t1", label: "the label", time: Date().unixTimestampInMiliseconds(), changeNumber: 1, storageId: randomId ? UUID().uuidString : "idFeature") + } + private func decorate(_ impression: KeyImpression, track: Bool = true) -> DecoratedImpression { + return DecoratedImpression(impression: impression, trackImpressions: track) } private func createImpressionsTracker(impressionsMode: ImpressionsMode, From 326a176b2a1896e10b04e4efb3496987f9657fda Mon Sep 17 00:00:00 2001 From: gthea Date: Fri, 20 Dec 2024 18:04:47 -0300 Subject: [PATCH 05/10] E2E tests for impressions toggling (#600) --- Split.xcodeproj/project.pbxproj | 8 + Split/Api/SplitApiFacade.swift | 23 +- Split/Api/SplitDatabaseHelper.swift | 5 +- .../SplitComponentFactory.swift | 7 +- .../RetryableSplitsUpdateWorkerFactory.swift | 47 ++-- Split/Network/Sync/ImpressionsTracker.swift | 15 +- Split/Network/Sync/SyncCommons.swift | 2 +- SplitTests/Fake/SyncWorkerFactoryStub.swift | 8 +- .../Impressions/ImpressionsToggleTest.swift | 254 ++++++++++++++++++ SplitTests/Resources/splitchanges_toggle.json | 124 +++++++++ 10 files changed, 421 insertions(+), 72 deletions(-) create mode 100644 SplitTests/Impressions/ImpressionsToggleTest.swift create mode 100644 SplitTests/Resources/splitchanges_toggle.json diff --git a/Split.xcodeproj/project.pbxproj b/Split.xcodeproj/project.pbxproj index 071263f7..b15548ca 100644 --- a/Split.xcodeproj/project.pbxproj +++ b/Split.xcodeproj/project.pbxproj @@ -1109,6 +1109,8 @@ C5A3F5212BEBDAD6009FACD3 /* HttpParameter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5A3F5202BEBDAD6009FACD3 /* HttpParameter.swift */; }; C5BD1E4C2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; C5BD1E4D2D11A993008EF198 /* DecoratedImpression.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */; }; + C5BD1E4F2D130EAF008EF198 /* ImpressionsToggleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5BD1E4E2D130EA7008EF198 /* ImpressionsToggleTest.swift */; }; + C5BD1E522D130FB6008EF198 /* splitchanges_toggle.json in Resources */ = {isa = PBXBuildFile; fileRef = C5BD1E512D130FB6008EF198 /* splitchanges_toggle.json */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1922,6 +1924,8 @@ C5A3F51E2BEBD893009FACD3 /* Spec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Spec.swift; sourceTree = ""; }; C5A3F5202BEBDAD6009FACD3 /* HttpParameter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpParameter.swift; sourceTree = ""; }; C5BD1E4B2D11A98E008EF198 /* DecoratedImpression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecoratedImpression.swift; sourceTree = ""; }; + C5BD1E4E2D130EA7008EF198 /* ImpressionsToggleTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImpressionsToggleTest.swift; sourceTree = ""; }; + C5BD1E512D130FB6008EF198 /* splitchanges_toggle.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = splitchanges_toggle.json; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2832,6 +2836,7 @@ C5977C312BF4031A003E293A /* split_changes_semver.json */, C58F33722BDAC4AC00D66549 /* split_unsupported_matcher.json */, C5977C582BF53F43003E293A /* splitchanges_unsupported.json */, + C5BD1E512D130FB6008EF198 /* splitchanges_toggle.json */, ); path = Resources; sourceTree = ""; @@ -3124,6 +3129,7 @@ 953911AB267A648F0040433A /* Impressions */ = { isa = PBXGroup; children = ( + C5BD1E4E2D130EA7008EF198 /* ImpressionsToggleTest.swift */, 955AAB912840182800ADFB07 /* ImpressionsNoneTest.swift */, 95CC53C52695F68500A07A04 /* ImpressionsDedupTest.swift */, 953911AC267A64FE0040433A /* ImpresionsObserverTest.swift */, @@ -3784,6 +3790,7 @@ 5912D14D2199FD4D00BC698C /* legacy_1.csv in Resources */, 95737E142ADEC71A007FD15C /* push_msg-splits_updV2.txt in Resources */, 95F7BBDE2C1A27C200C5F2E4 /* rsa_3072_public_01.pem in Resources */, + C5BD1E522D130FB6008EF198 /* splitchanges_toggle.json in Resources */, 95F7BBDA2C1A27C200C5F2E4 /* rsa_2048_public_01.pem in Resources */, 592C6AED211CBBB0002D120C /* splitchanges_2.json in Resources */, 950F72FF292E8D6C008A0040 /* SplitiOSFull.xctestplan in Resources */, @@ -4351,6 +4358,7 @@ 59F4AAA124FFC94100A1C69A /* NotificationManagerKeeperTest.swift in Sources */, 955E12372BFCDEAC00AE6D10 /* HashedImpressionDaoMock.swift in Sources */, 95ABF4FC29369B73006ED016 /* TelemetrySynchronizerStub.swift in Sources */, + C5BD1E4F2D130EAF008EF198 /* ImpressionsToggleTest.swift in Sources */, 95715A8529D353C100A1B2F9 /* DbCipherTest.swift in Sources */, 59D84BDC2215CD52003DA248 /* LocalhostSplitLoaderTests.swift in Sources */, 956D17D1260D266E0037F575 /* SplitsChangesCheckerTest.swift in Sources */, diff --git a/Split/Api/SplitApiFacade.swift b/Split/Api/SplitApiFacade.swift index eedcf317..69470452 100644 --- a/Split/Api/SplitApiFacade.swift +++ b/Split/Api/SplitApiFacade.swift @@ -16,13 +16,13 @@ struct SplitApiFacade { let splitsFetcher: HttpSplitFetcher let mySegmentsFetcher: HttpMySegmentsFetcher let impressionsRecorder: HttpImpressionsRecorder? - let impressionsCountRecorder: HttpImpressionsCountRecorder? + let impressionsCountRecorder: HttpImpressionsCountRecorder let eventsRecorder: HttpEventsRecorder let streamingHttpClient: HttpClient? let sseAuthenticator: SseAuthenticator let telemetryConfigRecorder: HttpTelemetryConfigRecorder? let telemetryStatsRecorder: HttpTelemetryStatsRecorder? - let uniqueKeysRecorder: HttpUniqueKeysRecorder? + let uniqueKeysRecorder: HttpUniqueKeysRecorder } class SplitApiFacadeBuilder { @@ -129,22 +129,15 @@ class SplitApiFacadeBuilder { return nil } - private func getImpressionsCountRecorder(restClient: RestClientImpressionsCount) -> HttpImpressionsCountRecorder? { - if impressionsMode() == .optimized || - impressionsMode() == .none { - let syncHelper = DefaultSyncHelper(telemetryProducer: telemetryStorage) - return DefaultHttpImpressionsCountRecorder(restClient: restClient, - syncHelper: syncHelper) - } - return nil + private func getImpressionsCountRecorder(restClient: RestClientImpressionsCount) -> HttpImpressionsCountRecorder { + let syncHelper = DefaultSyncHelper(telemetryProducer: telemetryStorage) + return DefaultHttpImpressionsCountRecorder(restClient: restClient, + syncHelper: syncHelper) } - private func getUniqueKeysRecorder(restClient: RestClientUniqueKeys) -> HttpUniqueKeysRecorder? { - if impressionsMode() == .none { - return DefaultHttpUniqueKeysRecorder(restClient: restClient, + private func getUniqueKeysRecorder(restClient: RestClientUniqueKeys) -> HttpUniqueKeysRecorder { + return DefaultHttpUniqueKeysRecorder(restClient: restClient, syncHelper: DefaultSyncHelper(telemetryProducer: telemetryStorage)) - } - return nil } private func impressionsMode() -> ImpressionsMode { diff --git a/Split/Api/SplitDatabaseHelper.swift b/Split/Api/SplitDatabaseHelper.swift index c45d4e3e..e32c3ee8 100644 --- a/Split/Api/SplitDatabaseHelper.swift +++ b/Split/Api/SplitDatabaseHelper.swift @@ -117,12 +117,9 @@ struct SplitDatabaseHelper { let attributesStorage = openAttributesStorage(database: splitDatabase, splitClientConfig: splitClientConfig) - var uniqueKeyStorage: PersistentUniqueKeysStorage? - if splitClientConfig.$impressionsMode == .none { - uniqueKeyStorage = + let uniqueKeyStorage: PersistentUniqueKeysStorage = DefaultPersistentUniqueKeysStorage(database: splitDatabase, expirationPeriod: kExpirationPeriod) - } let persistentHashedImpressionsStorage = DefaultPersistentHashedImpressionsStorage(database: splitDatabase) let hashedImpressionsStorage = DefaultHashedImpressionsStorage( diff --git a/Split/Initialization/SplitComponentFactory.swift b/Split/Initialization/SplitComponentFactory.swift index 2e325552..1678c6ee 100644 --- a/Split/Initialization/SplitComponentFactory.swift +++ b/Split/Initialization/SplitComponentFactory.swift @@ -147,11 +147,8 @@ class SplitComponentFactory { func buildImpressionsTracker(notificationHelper: NotificationHelper?) throws -> ImpressionsTracker { let storageContainer = try getSplitStorageContainer() - var uniqueKeyTracker: UniqueKeyTracker? - if splitClientConfig.$impressionsMode == .none, - let uniqueKeyStorage = storageContainer.uniqueKeyStorage { - uniqueKeyTracker = DefaultUniqueKeyTracker(persistentUniqueKeyStorage: uniqueKeyStorage) - } + var uniqueKeyTracker = DefaultUniqueKeyTracker(persistentUniqueKeyStorage: storageContainer.uniqueKeyStorage) + let component: ImpressionsTracker = DefaultImpressionsTracker( splitConfig: splitClientConfig, diff --git a/Split/Network/Streaming/RetryableSplitsUpdateWorkerFactory.swift b/Split/Network/Streaming/RetryableSplitsUpdateWorkerFactory.swift index 0e452223..5f6cced1 100644 --- a/Split/Network/Streaming/RetryableSplitsUpdateWorkerFactory.swift +++ b/Split/Network/Streaming/RetryableSplitsUpdateWorkerFactory.swift @@ -33,17 +33,17 @@ protocol SyncWorkerFactory { func createImpressionsRecorderWorker(syncHelper: ImpressionsRecorderSyncHelper?) -> RecorderWorker? - func createImpressionsCountRecorderWorker() -> RecorderWorker? + func createImpressionsCountRecorderWorker() -> RecorderWorker - func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker? + func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker func createPeriodicEventsRecorderWorker(syncHelper: EventsRecorderSyncHelper?) -> PeriodicRecorderWorker func createEventsRecorderWorker(syncHelper: EventsRecorderSyncHelper?) -> RecorderWorker - func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker? + func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker - func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker? + func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker func createTelemetryConfigRecorderWorker() -> RecorderWorker? @@ -145,24 +145,14 @@ class DefaultSyncWorkerFactory: SyncWorkerFactory { impressionsSyncHelper: syncHelper) } - func createImpressionsCountRecorderWorker() -> RecorderWorker? { - - guard let impressionsCountRecorder = apiFacade.impressionsCountRecorder else { - return nil - } - + func createImpressionsCountRecorderWorker() -> RecorderWorker { return ImpressionsCountRecorderWorker(countsStorage: storageContainer.impressionsCountStorage, - countsRecorder: impressionsCountRecorder) + countsRecorder: apiFacade.impressionsCountRecorder) } - func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker? { - - guard let impressionsCountRecorder = apiFacade.impressionsCountRecorder else { - return nil - } - + func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker { let recorderWorker = ImpressionsCountRecorderWorker(countsStorage: storageContainer.impressionsCountStorage, - countsRecorder: impressionsCountRecorder) + countsRecorder: apiFacade.impressionsCountRecorder) let timer = DefaultPeriodicTimer(deadline: 0, interval: splitConfig.impressionsCountsRefreshRate) return DefaultPeriodicRecorderWorker(timer: timer, recorderWorker: recorderWorker) } @@ -240,23 +230,16 @@ class DefaultSyncWorkerFactory: SyncWorkerFactory { return DefaultPeriodicRecorderWorker(timer: timer, recorderWorker: telemetryStatsWorker) } - func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker? { - if let recorder = apiFacade.uniqueKeysRecorder, let storage = storageContainer.uniqueKeyStorage { - return UniqueKeysRecorderWorker(uniqueKeyStorage: storage, - uniqueKeysRecorder: recorder, + func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker { + return UniqueKeysRecorderWorker(uniqueKeyStorage: storageContainer.uniqueKeyStorage, + uniqueKeysRecorder: apiFacade.uniqueKeysRecorder, flushChecker: flusherChecker) - } - return nil } - func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker? { - if let worker = createUniqueKeyRecorderWorker(flusherChecker: flusherChecker) { - let timer = DefaultPeriodicTimer(deadline: splitConfig.uniqueKeysRefreshRate, - interval: splitConfig.uniqueKeysRefreshRate) - return DefaultPeriodicRecorderWorker(timer: timer, - recorderWorker: worker) - } - return nil + func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker { + return DefaultPeriodicRecorderWorker(timer: DefaultPeriodicTimer(deadline: splitConfig.uniqueKeysRefreshRate, + interval: splitConfig.uniqueKeysRefreshRate), + recorderWorker: createUniqueKeyRecorderWorker(flusherChecker: flusherChecker)) } } diff --git a/Split/Network/Sync/ImpressionsTracker.swift b/Split/Network/Sync/ImpressionsTracker.swift index 67b691af..b302f859 100644 --- a/Split/Network/Sync/ImpressionsTracker.swift +++ b/Split/Network/Sync/ImpressionsTracker.swift @@ -207,18 +207,11 @@ class DefaultImpressionsTracker: ImpressionsTracker { private func setupImpressionsMode() { - switch splitConfig.$impressionsMode { - case .optimized: - createImpressionsRecorder() - createUniqueKeysRecorder() - createImpressionsCountRecorder() - case .debug: + createUniqueKeysRecorder() + createImpressionsCountRecorder() + + if splitConfig.$impressionsMode == .debug || splitConfig.$impressionsMode == .optimized { createImpressionsRecorder() - createUniqueKeysRecorder() - createImpressionsCountRecorder() - case .none: - createUniqueKeysRecorder() - createImpressionsCountRecorder() } } diff --git a/Split/Network/Sync/SyncCommons.swift b/Split/Network/Sync/SyncCommons.swift index 2771c417..5ebcca25 100644 --- a/Split/Network/Sync/SyncCommons.swift +++ b/Split/Network/Sync/SyncCommons.swift @@ -19,7 +19,7 @@ struct SplitStorageContainer { let mySegmentsStorage: MySegmentsStorage let myLargeSegmentsStorage: MySegmentsStorage let attributesStorage: AttributesStorage - let uniqueKeyStorage: PersistentUniqueKeysStorage? + let uniqueKeyStorage: PersistentUniqueKeysStorage let flagSetsCache: FlagSetsCache let persistentHashedImpressionsStorage: PersistentHashedImpressionsStorage let hashedImpressionsStorage: HashedImpressionsStorage diff --git a/SplitTests/Fake/SyncWorkerFactoryStub.swift b/SplitTests/Fake/SyncWorkerFactoryStub.swift index 319032c5..a9769500 100644 --- a/SplitTests/Fake/SyncWorkerFactoryStub.swift +++ b/SplitTests/Fake/SyncWorkerFactoryStub.swift @@ -65,11 +65,11 @@ class SyncWorkerFactoryStub: SyncWorkerFactory { return impressionsRecorderWorker } - func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker? { + func createPeriodicImpressionsCountRecorderWorker() -> PeriodicRecorderWorker { return periodicImpressionsCountRecorderWorker } - func createImpressionsCountRecorderWorker() -> RecorderWorker? { + func createImpressionsCountRecorderWorker() -> RecorderWorker { return impressionsCountRecorderWorker } @@ -93,11 +93,11 @@ class SyncWorkerFactoryStub: SyncWorkerFactory { return periodicTelemetryStatsRecorderWorker } - func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker? { + func createUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> RecorderWorker { return uniqueKeysRecorderWorker } - func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker? { + func createPeriodicUniqueKeyRecorderWorker(flusherChecker: RecorderFlushChecker?) -> PeriodicRecorderWorker { return periodicUniqueKeysRecorderWorker } } diff --git a/SplitTests/Impressions/ImpressionsToggleTest.swift b/SplitTests/Impressions/ImpressionsToggleTest.swift new file mode 100644 index 00000000..bcf3501b --- /dev/null +++ b/SplitTests/Impressions/ImpressionsToggleTest.swift @@ -0,0 +1,254 @@ +// +// ImpressionsToggleTest.swift +// Split +// +// Created by Gaston Thea on 18/12/2024. +// Copyright © 2024 Split. All rights reserved. +// + +import Foundation + +import XCTest +@testable import Split + +class ImpressionsToggleTest: XCTestCase { + + var httpClient: HttpClient! + let apiKey = IntegrationHelper.dummyApiKey + let userKey = "key" + var isSseAuthHit = false + var isSseHit = false + var streamingBinding: TestStreamResponseBinding? + var firstSplitHit = true + var sseExp: XCTestExpectation! + var impExp: XCTestExpectation? + var uniqueExp: XCTestExpectation? + var countExp: XCTestExpectation? + var uniqueKeys: [UniqueKeys]! + var counts: [String: Int]! + var impressionsHitCount = 0 + var impressions: [ImpressionsTest]! + let queue = DispatchQueue(label: "queue", target: .test) + var notificationHelper: NotificationHelperStub? + + override func setUp() { + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), + streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + uniqueKeys = [UniqueKeys]() + counts = [String: Int]() + impressions = [ImpressionsTest]() + sseExp = XCTestExpectation(description: "Sse conn") + impExp = XCTestExpectation(description: "Imp exp") + uniqueExp = XCTestExpectation(description: "Unique exp") + countExp = XCTestExpectation(description: "Count exp") + impressionsHitCount = 0 + notificationHelper = NotificationHelperStub() + } + + func testManagerContainsProperty() { + let factory = getReadyFactory(impressionsMode: "optimized") + let manager = factory.manager + + let splits = manager.splits + + XCTAssertTrue(splits.filter { $0.trackImpressions && $0.name == "tracked" }.count == 1) + XCTAssertTrue(splits.filter { !$0.trackImpressions && $0.name == "not_tracked" }.count == 1) + } + + func testDebugMode() { + let client = getTreatments(impressionsMode: "debug") + + XCTAssertEqual(uniqueKeys.count, 1) + XCTAssertEqual(counts["not_tracked"], 1) + XCTAssertNil(counts["tracked"]) + XCTAssertEqual(impressions.count, 1) + XCTAssertTrue(impressions[0].testName == "tracked") + XCTAssertTrue(impressions[0].keyImpressions.count == 1) + + destroy(client) + } + + func testOptimizedMode() { + let client = getTreatments(impressionsMode: "optimized") + + XCTAssertEqual(uniqueKeys.count, 1) + XCTAssertEqual(counts["not_tracked"], 1) + XCTAssertNil(counts["tracked"]) + XCTAssertEqual(impressions.count, 1) + XCTAssertTrue(impressions[0].testName == "tracked") + XCTAssertTrue(impressions[0].keyImpressions.count == 1) + + destroy(client) + } + + func testNoneMode() { + let client = getTreatments(impressionsMode: "none") + + XCTAssertEqual(uniqueKeys.count, 1) + XCTAssertEqual(counts["not_tracked"], 1) + XCTAssertEqual(counts["tracked"], 1) + XCTAssertTrue(impressions.isEmpty) + + destroy(client) + } + + private func getTreatments(impressionsMode: String) -> SplitClient { + let factory = getReadyFactory(impressionsMode: impressionsMode) + let client = factory.client + + _ = client.getTreatment("tracked") + _ = client.getTreatment("not_tracked") + + sleep(1) + + // Unique keys and impressions count are saved on app bg + // Here that situation is simulated + notificationHelper!.simulateApplicationDidEnterBackground() + // Make app active again + notificationHelper!.simulateApplicationDidBecomeActive() + + client.flush() + // Unique key should arrive if periodic recording works + var exps = [XCTestExpectation]() + exps.append(uniqueExp!) + exps.append(countExp!) + if impressionsMode != "none" { + exps.append(impExp!) + } + wait(for: exps, timeout: 5) + + return client + } + + private func destroy(_ client: SplitClient) { + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } + + private func getReadyFactory(impressionsMode: String) -> SplitFactory { + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.impressionsMode = impressionsMode + splitConfig.uniqueKeysRefreshRate = 1 + splitConfig.impressionRefreshRate = 1 + splitConfig.impressionsCountsRefreshRate = 1 + splitConfig.logLevel = .verbose + + let key: Key = Key(matchingKey: userKey) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setHttpClient(httpClient) + _ = builder.setReachabilityChecker(ReachabilityMock()) + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "test")) + _ = builder.setNotificationHelper(notificationHelper!) + let factory = builder.setApiKey(apiKey).setKey(key) + .setConfig(splitConfig).build()! + + var exps = [XCTestExpectation]() + let client = factory.client + + let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") + + exps.append(sdkReadyExpectation) + + client.on(event: SplitEvent.sdkReady) { + sdkReadyExpectation.fulfill() + } + + client.on(event: SplitEvent.sdkReadyTimedOut) { + sdkReadyExpectation.fulfill() + } + + exps.append(sseExp) + wait(for: exps, timeout: 5) + + return factory + } + + private func buildTestDispatcher() -> HttpClientTestDispatcher { + return { request in + if request.isSplitEndpoint() { + if self.firstSplitHit { + self.firstSplitHit = false + return TestDispatcherResponse(code: 200, data: Data(self.loadSplitsChangeFile().utf8)) + } + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptySplitChanges(since: 99999, till: 99999).utf8)) + } + + if request.isMySegmentsEndpoint() { + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.emptyMySegments.utf8)) + } + + if request.isAuthEndpoint() { + self.isSseAuthHit = true + return TestDispatcherResponse(code: 200, data: Data(IntegrationHelper.dummySseResponse().utf8)) + } + + if request.isImpressionsEndpoint() { + self.impressionsHitCount+=1 + self.queue.sync { + if let exp = self.impExp { + exp.fulfill() + } + if let body = request.body?.stringRepresentation.utf8 { + if let impressions = try? Json.decodeFrom(json: String(body), to: [ImpressionsTest].self) { + self.impressions = impressions + } + } + } + return TestDispatcherResponse(code: 200) + } + + if request.isImpressionsCountEndpoint() { + self.queue.sync { + if let exp = self.countExp { + exp.fulfill() + } + if let body = request.body?.stringRepresentation.utf8 { + if let counts = try? Json.decodeFrom(json: String(body), to: ImpressionsCount.self) { + for count in counts.perFeature { + self.counts[count.feature] = count.count + (self.counts[count.feature] ?? 0) + } + } + } + } + return TestDispatcherResponse(code: 200) + } + + if request.isUniqueKeysEndpoint() { + self.queue.sync { + if let body = request.body?.stringRepresentation.utf8 { + if let keys = try? Json.decodeFrom(json: String(body), to: UniqueKeys.self) { + self.uniqueKeys.append(keys) + } + } + if let exp = self.uniqueExp { + exp.fulfill() + } + } + return TestDispatcherResponse(code: 200) + } + return TestDispatcherResponse(code: 200) + } + } + + private func loadSplitsChangeFile() -> String { + guard let splitJson = FileHelper.readDataFromFile(sourceClass: self, name: "splitchanges_toggle", type: "json") else { + return IntegrationHelper.emptySplitChanges(since: 99999, till: 99999) + } + return splitJson + + } + + private func buildStreamingHandler() -> TestStreamResponseBindingHandler { + return { request in + self.isSseHit = true + self.streamingBinding = TestStreamResponseBinding.createFor(request: request, code: 200) + self.sseExp.fulfill() + return self.streamingBinding! + } + } +} diff --git a/SplitTests/Resources/splitchanges_toggle.json b/SplitTests/Resources/splitchanges_toggle.json new file mode 100644 index 00000000..d1f7196c --- /dev/null +++ b/SplitTests/Resources/splitchanges_toggle.json @@ -0,0 +1,124 @@ +{ + "splits": [ + { + "trafficTypeName": "user", + "name": "tracked", + "trafficAllocation": 100, + "trafficAllocationSeed": -285565213, + "seed": -1992295819, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1506703262916, + "algo": 2, + "trackImpressions": true, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + } + ] + }, + { + "trafficTypeName": "user", + "name": "not_tracked", + "trafficAllocation": 100, + "trafficAllocationSeed": -285565213, + "seed": -1992295819, + "status": "ACTIVE", + "killed": false, + "defaultTreatment": "off", + "changeNumber": 1506703262916, + "algo": 2, + "trackImpressions": false, + "conditions": [ + { + "conditionType": "ROLLOUT", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "client", + "attribute": null + }, + "matcherType": "IN_SEGMENT", + "negate": false, + "userDefinedSegmentMatcherData": { + "segmentName": "new_segment" + }, + "whitelistMatcherData": null, + "unaryNumericMatcherData": null, + "betweenMatcherData": null, + "booleanMatcherData": null, + "dependencyMatcherData": null, + "stringMatcherData": null + } + ] + }, + "partitions": [ + { + "treatment": "on", + "size": 0 + }, + { + "treatment": "off", + "size": 0 + }, + { + "treatment": "free", + "size": 100 + }, + { + "treatment": "conta", + "size": 0 + } + ], + "label": "in segment new_segment" + } + ] + } + ], + "since": 1506703262916, + "till": 1506703262916 +} From 37cb442b06b0a59e7fd61030e695df8b8cff4602 Mon Sep 17 00:00:00 2001 From: gthea Date: Tue, 7 Jan 2025 18:51:43 -0300 Subject: [PATCH 06/10] Rename trackImpressions property (#602) --- Split/Api/DefaultSplitManager.swift | 2 +- Split/Api/SplitView.swift | 2 +- Split/Engine/DefaultTreatmentManager.swift | 8 +++---- Split/Engine/Evaluator.swift | 21 ++++++++++--------- Split/Impressions/DecoratedImpression.swift | 6 +++--- Split/Models/SplitModel/Split.swift | 8 +++---- Split/Network/Sync/ImpressionsTracker.swift | 2 +- SplitTests/EvaluatorTests.swift | 20 +++++++++--------- .../Impressions/ImpressionsToggleTest.swift | 4 ++-- SplitTests/Resources/splitchanges_toggle.json | 4 ++-- SplitTests/Resources/splits.json | 2 +- SplitTests/SplitManagerTest.swift | 4 ++-- .../Streaming/ImpressionsTrackerTest.swift | 8 +++---- 13 files changed, 46 insertions(+), 45 deletions(-) diff --git a/Split/Api/DefaultSplitManager.swift b/Split/Api/DefaultSplitManager.swift index be8efc07..98e037ab 100644 --- a/Split/Api/DefaultSplitManager.swift +++ b/Split/Api/DefaultSplitManager.swift @@ -43,7 +43,7 @@ import Foundation splitView.killed = split.killed splitView.sets = Array(split.sets ?? []) splitView.configs = split.configurations ?? [String: String]() - splitView.trackImpressions = split.trackImpressions ?? true + splitView.impressionsDisabled = split.impressionsDisabled ?? false if let conditions = split.conditions { var treatments = Set() diff --git a/Split/Api/SplitView.swift b/Split/Api/SplitView.swift index bf828a5d..0c665ec4 100644 --- a/Split/Api/SplitView.swift +++ b/Split/Api/SplitView.swift @@ -26,5 +26,5 @@ public class SplitView: NSObject, Codable { } @objc public var configs: [String: String]? - @objc public var trackImpressions: Bool = true + @objc public var impressionsDisabled: Bool = false } diff --git a/Split/Engine/DefaultTreatmentManager.swift b/Split/Engine/DefaultTreatmentManager.swift index 2d447f9a..9238f704 100644 --- a/Split/Engine/DefaultTreatmentManager.swift +++ b/Split/Engine/DefaultTreatmentManager.swift @@ -235,11 +235,11 @@ extension DefaultTreatmentManager { validationTag: validationTag) logImpression(label: result.label, changeNumber: result.changeNumber, treatment: result.treatment, splitName: trimmedSplitName, attributes: mergedAttributes, - trackImpressions: result.trackImpressions) + impressionsDisabled: result.impressionsDisabled) return SplitResult(treatment: result.treatment, config: result.configuration) } catch { logImpression(label: ImpressionsConstants.exception, treatment: SplitConstants.control, - splitName: trimmedSplitName, attributes: mergedAttributes, trackImpressions: true) + splitName: trimmedSplitName, attributes: mergedAttributes, impressionsDisabled: false) return SplitResult(treatment: SplitConstants.control) } } @@ -265,7 +265,7 @@ extension DefaultTreatmentManager { private func logImpression(label: String, changeNumber: Int64? = nil, treatment: String, splitName: String, attributes: [String: Any]? = nil, - trackImpressions: Bool) { + impressionsDisabled: Bool) { let keyImpression = KeyImpression(featureName: splitName, keyName: key.matchingKey, @@ -275,7 +275,7 @@ extension DefaultTreatmentManager { time: Date().unixTimestampInMiliseconds(), changeNumber: changeNumber) impressionLogger.pushImpression( - impression: DecoratedImpression(impression: keyImpression, trackImpressions: trackImpressions)) + impression: DecoratedImpression(impression: keyImpression, impressionsDisabled: impressionsDisabled)) if let externalImpressionHandler = splitConfig.impressionListener { let impression = keyImpression.toImpression() diff --git a/Split/Engine/Evaluator.swift b/Split/Engine/Evaluator.swift index 4f2655ed..dab5a12f 100644 --- a/Split/Engine/Evaluator.swift +++ b/Split/Engine/Evaluator.swift @@ -12,14 +12,15 @@ struct EvaluationResult { var label: String var changeNumber: Int64? var configuration: String? - var trackImpressions: Bool + var impressionsDisabled: Bool - init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil, trackImpressions: Bool = true) { + init(treatment: String, label: String, changeNumber: Int64? = nil, configuration: String? = nil, + impressionsDisabled: Bool = false) { self.treatment = treatment self.label = label self.changeNumber = changeNumber self.configuration = configuration - self.trackImpressions = trackImpressions + self.impressionsDisabled = impressionsDisabled } } @@ -80,7 +81,7 @@ class DefaultEvaluator: Evaluator { label: ImpressionsConstants.killed, changeNumber: changeNumber, configuration: split.configurations?[defaultTreatment], - trackImpressions: split.shouldTrackImpression()) + impressionsDisabled: split.isImpressionsDisabled()) } var inRollOut: Bool = false @@ -110,7 +111,7 @@ class DefaultEvaluator: Evaluator { label: ImpressionsConstants.notInSplit, changeNumber: changeNumber, configuration: split.configurations?[defaultTreatment], - trackImpressions: split.shouldTrackImpression()) + impressionsDisabled: split.isImpressionsDisabled()) } inRollOut = true } @@ -126,19 +127,19 @@ class DefaultEvaluator: Evaluator { return EvaluationResult(treatment: treatment, label: condition.label!, changeNumber: changeNumber, configuration: split.configurations?[treatment], - trackImpressions: split.shouldTrackImpression()) + impressionsDisabled: split.isImpressionsDisabled()) } } let result = EvaluationResult(treatment: defaultTreatment, label: ImpressionsConstants.noConditionMatched, changeNumber: changeNumber, configuration: split.configurations?[defaultTreatment], - trackImpressions: split.shouldTrackImpression()) + impressionsDisabled: split.isImpressionsDisabled()) return result } catch EvaluatorError.matcherNotFound { Logger.e("The matcher has not been found") return EvaluationResult(treatment: SplitConstants.control, label: ImpressionsConstants.matcherNotFound, - changeNumber: changeNumber, trackImpressions: split.shouldTrackImpression()) + changeNumber: changeNumber, impressionsDisabled: split.isImpressionsDisabled()) } } @@ -157,7 +158,7 @@ class DefaultEvaluator: Evaluator { } private extension Split { - func shouldTrackImpression() -> Bool { - return self.trackImpressions ?? true + func isImpressionsDisabled() -> Bool { + return self.impressionsDisabled ?? false } } diff --git a/Split/Impressions/DecoratedImpression.swift b/Split/Impressions/DecoratedImpression.swift index ea6a26f3..f7968bbd 100644 --- a/Split/Impressions/DecoratedImpression.swift +++ b/Split/Impressions/DecoratedImpression.swift @@ -7,10 +7,10 @@ struct DecoratedImpression { let impression: KeyImpression - let trackImpressions: Bool + let impressionsDisabled: Bool - init(impression: KeyImpression, trackImpressions: Bool) { + init(impression: KeyImpression, impressionsDisabled: Bool) { self.impression = impression - self.trackImpressions = trackImpressions + self.impressionsDisabled = impressionsDisabled } } diff --git a/Split/Models/SplitModel/Split.swift b/Split/Models/SplitModel/Split.swift index b90b4266..452deacf 100644 --- a/Split/Models/SplitModel/Split.swift +++ b/Split/Models/SplitModel/Split.swift @@ -24,13 +24,13 @@ class SplitDTO: NSObject, SplitBase, Codable { var algo: Int? var configurations: [String: String]? var sets: Set? - var trackImpressions: Bool? + var impressionsDisabled: Bool? var json: String = "" var isParsed = true - init(name: String, trafficType: String, status: Status, sets: Set?, json: String, killed: Bool = false, trackImpressions: Bool = true) { + init(name: String, trafficType: String, status: Status, sets: Set?, json: String, killed: Bool = false, impressionsDisabled: Bool = false) { self.name = name self.trafficTypeName = trafficType self.status = status @@ -38,7 +38,7 @@ class SplitDTO: NSObject, SplitBase, Codable { self.json = json self.killed = killed self.isParsed = false - self.trackImpressions = trackImpressions + self.impressionsDisabled = impressionsDisabled } enum CodingKeys: String, CodingKey { @@ -55,6 +55,6 @@ class SplitDTO: NSObject, SplitBase, Codable { case algo case configurations case sets - case trackImpressions + case impressionsDisabled } } diff --git a/Split/Network/Sync/ImpressionsTracker.swift b/Split/Network/Sync/ImpressionsTracker.swift index b302f859..9acfcb9b 100644 --- a/Split/Network/Sync/ImpressionsTracker.swift +++ b/Split/Network/Sync/ImpressionsTracker.swift @@ -110,7 +110,7 @@ class DefaultImpressionsTracker: ImpressionsTracker { return } - if isNoneImpressionsMode() || !decoratedImpression.trackImpressions { + if isNoneImpressionsMode() || decoratedImpression.impressionsDisabled { uniqueKeyTracker?.track(userKey: impression.keyName, featureName: featureName) impressionsCounter?.inc(featureName: featureName, timeframe: impression.time, amount: 1) if uniqueKeyFlushChecker? diff --git a/SplitTests/EvaluatorTests.swift b/SplitTests/EvaluatorTests.swift index b659ca08..37bbbe71 100644 --- a/SplitTests/EvaluatorTests.swift +++ b/SplitTests/EvaluatorTests.swift @@ -371,19 +371,19 @@ class EvaluatorTests: XCTestCase { inLargeSegmentWhiteListTest(key: "the_bad_key", treatment: "off") } - func testTrackImpressionsNil() { - withTrackImpressions(nil) + func testimpressionsDisabledNil() { + withImpressionsDisabled(nil) } - func testTrackImpressionsTrue() { - withTrackImpressions(true) + func testImpressionsDisabledTrue() { + withImpressionsDisabled(true) } - func testTrackImpressionsFalse() { - withTrackImpressions(false) + func testImpressionsDisabledFalse() { + withImpressionsDisabled(false) } - private func withTrackImpressions(_ track: Bool?) { + private func withImpressionsDisabled(_ disabled: Bool?) { var result: EvaluationResult! var evaluator: Evaluator! guard let split = loadSplit(splitName: "split_sample_feature6") else { @@ -391,15 +391,15 @@ class EvaluatorTests: XCTestCase { return } split.algo = 2 - if (track != nil) { - split.trackImpressions = track + if (disabled != nil) { + split.impressionsDisabled = disabled } evaluator = customEvaluator(split: split) result = try? evaluator.evalTreatment(matchingKey: matchingKey, bucketingKey: nil, splitName: split.name!, attributes: nil) XCTAssertNotNil(result) XCTAssertEqual("t4_6", result?.treatment) - XCTAssertEqual(track ?? true, result!.trackImpressions) + XCTAssertEqual(disabled ?? false, result!.impressionsDisabled) } func inLargeSegmentWhiteListTest(key: String, treatment: String = "on") { diff --git a/SplitTests/Impressions/ImpressionsToggleTest.swift b/SplitTests/Impressions/ImpressionsToggleTest.swift index bcf3501b..ddd60e75 100644 --- a/SplitTests/Impressions/ImpressionsToggleTest.swift +++ b/SplitTests/Impressions/ImpressionsToggleTest.swift @@ -53,8 +53,8 @@ class ImpressionsToggleTest: XCTestCase { let splits = manager.splits - XCTAssertTrue(splits.filter { $0.trackImpressions && $0.name == "tracked" }.count == 1) - XCTAssertTrue(splits.filter { !$0.trackImpressions && $0.name == "not_tracked" }.count == 1) + XCTAssertTrue(splits.filter { !$0.impressionsDisabled && $0.name == "tracked" }.count == 1) + XCTAssertTrue(splits.filter { $0.impressionsDisabled && $0.name == "not_tracked" }.count == 1) } func testDebugMode() { diff --git a/SplitTests/Resources/splitchanges_toggle.json b/SplitTests/Resources/splitchanges_toggle.json index d1f7196c..2d468267 100644 --- a/SplitTests/Resources/splitchanges_toggle.json +++ b/SplitTests/Resources/splitchanges_toggle.json @@ -11,7 +11,7 @@ "defaultTreatment": "off", "changeNumber": 1506703262916, "algo": 2, - "trackImpressions": true, + "impressionsDisabled": false, "conditions": [ { "conditionType": "ROLLOUT", @@ -70,7 +70,7 @@ "defaultTreatment": "off", "changeNumber": 1506703262916, "algo": 2, - "trackImpressions": false, + "impressionsDisabled": true, "conditions": [ { "conditionType": "ROLLOUT", diff --git a/SplitTests/Resources/splits.json b/SplitTests/Resources/splits.json index 3c1f1df4..113ecb63 100644 --- a/SplitTests/Resources/splits.json +++ b/SplitTests/Resources/splits.json @@ -10,7 +10,7 @@ "changeNumber": 1, "algo": 2, "sets": ["set1", "set2"], - "trackImpressions": false, + "impressionsDisabled": true, "configurations": { "t1": "{\"f1\": \"v1\"}", "t2": "{\"f2\": \"v2\"}" diff --git a/SplitTests/SplitManagerTest.swift b/SplitTests/SplitManagerTest.swift index 2c9ef321..196388d6 100644 --- a/SplitTests/SplitManagerTest.swift +++ b/SplitTests/SplitManagerTest.swift @@ -68,7 +68,7 @@ class SplitManagerTest: XCTestCase { XCTAssertEqual(split0?.trafficType, "custom", "Split0 traffic type") XCTAssertEqual(split0?.sets?.sorted(), ["set1", "set2"]) XCTAssertNotNil(split0?.configs) - XCTAssertFalse(split0?.trackImpressions ?? true, "Split0 track impressions") + XCTAssertTrue(split0?.impressionsDisabled ?? false, "Split0 track impressions") XCTAssertEqual(treatments0?.count, 6, "Split0 treatment count") XCTAssertEqual(treatments0?.sorted().joined(separator: ",").lowercased(), "t1_0,t2_0,t3_0,t4_0,t5_0,t6_0", "Split0 treatment names") @@ -87,7 +87,7 @@ class SplitManagerTest: XCTestCase { XCTAssertEqual(treatments1?.sorted().joined(separator: ",").lowercased(), "t1_1,t2_1,t3_1,t4_1,t5_1,t6_1", "Split1 treatment names") XCTAssertEqual([], splitWithoutSets!.sets!) - XCTAssertTrue(splitWithoutSets!.trackImpressions, "Split1 track impressions") + XCTAssertFalse(splitWithoutSets!.impressionsDisabled, "Split1 track impressions") } func testAddOneSplit() { diff --git a/SplitTests/Streaming/ImpressionsTrackerTest.swift b/SplitTests/Streaming/ImpressionsTrackerTest.swift index ad04628b..fb579d7d 100644 --- a/SplitTests/Streaming/ImpressionsTrackerTest.swift +++ b/SplitTests/Streaming/ImpressionsTrackerTest.swift @@ -35,7 +35,7 @@ class ImpressionsTrackerTest: XCTestCase { let impression = createImpression() for i in 0..<5 { - impressionsTracker.push(decorate(impression, track: i % 2 == 0)) + impressionsTracker.push(decorate(impression, impressionsDisabled: i % 2 != 0)) } // Before save an clear @@ -59,7 +59,7 @@ class ImpressionsTrackerTest: XCTestCase { for i in 0..<5 { impression.storageId = UUID().uuidString - impressionsTracker.push(decorate(impression, track: i % 2 == 0)) + impressionsTracker.push(decorate(impression, impressionsDisabled: i % 2 != 0)) } // Before save an clear @@ -308,8 +308,8 @@ class ImpressionsTrackerTest: XCTestCase { changeNumber: 1, storageId: randomId ? UUID().uuidString : "idFeature") } - private func decorate(_ impression: KeyImpression, track: Bool = true) -> DecoratedImpression { - return DecoratedImpression(impression: impression, trackImpressions: track) + private func decorate(_ impression: KeyImpression, impressionsDisabled: Bool = false) -> DecoratedImpression { + return DecoratedImpression(impression: impression, impressionsDisabled: impressionsDisabled) } private func createImpressionsTracker(impressionsMode: ImpressionsMode, From 15b8c4ff1c2fd1546fb754084ad6e3f25b7086f8 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Thu, 9 Jan 2025 16:12:28 -0300 Subject: [PATCH 07/10] xcbeautify --- .github/workflows/base_ut.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/base_ut.yaml b/.github/workflows/base_ut.yaml index 460ea296..8aedeaae 100644 --- a/.github/workflows/base_ut.yaml +++ b/.github/workflows/base_ut.yaml @@ -36,6 +36,7 @@ jobs: destination: ${{ inputs.destination }} project: Split.xcodeproj scheme: Split + output-formatter: 'xcbeautify' sdk: 'iphonesimulator' test-plan: ${{ inputs.test-plan }} From 7db5f74bcd4a17cfce0cfd46a9c4193d5a71f969 Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 13 Jan 2025 18:41:12 -0300 Subject: [PATCH 08/10] Version 3.1.0-rc1 --- Split.podspec | 2 +- Split/Common/Utils/Version.swift | 2 +- Split/Initialization/SplitComponentFactory.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Split.podspec b/Split.podspec index 760cc374..b928c5a7 100644 --- a/Split.podspec +++ b/Split.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Split' s.module_name = 'Split' - s.version = '3.0.0' + s.version = '3.1.0-rc1' s.summary = 'iOS SDK for Split' s.description = <<-DESC This SDK is designed to work with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. diff --git a/Split/Common/Utils/Version.swift b/Split/Common/Utils/Version.swift index 5cbd4013..61d92f48 100644 --- a/Split/Common/Utils/Version.swift +++ b/Split/Common/Utils/Version.swift @@ -9,7 +9,7 @@ import Foundation class Version { private static let kSdkPlatform: String = "ios" - private static let kVersion = "3.0.0" + private static let kVersion = "3.1.0-rc1" static var semantic: String { return kVersion diff --git a/Split/Initialization/SplitComponentFactory.swift b/Split/Initialization/SplitComponentFactory.swift index 1678c6ee..20681f10 100644 --- a/Split/Initialization/SplitComponentFactory.swift +++ b/Split/Initialization/SplitComponentFactory.swift @@ -147,7 +147,7 @@ class SplitComponentFactory { func buildImpressionsTracker(notificationHelper: NotificationHelper?) throws -> ImpressionsTracker { let storageContainer = try getSplitStorageContainer() - var uniqueKeyTracker = DefaultUniqueKeyTracker(persistentUniqueKeyStorage: storageContainer.uniqueKeyStorage) + let uniqueKeyTracker = DefaultUniqueKeyTracker(persistentUniqueKeyStorage: storageContainer.uniqueKeyStorage) let component: ImpressionsTracker = DefaultImpressionsTracker( From 14bedb8eac516546b52535904044a467327f17ce Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Fri, 17 Jan 2025 13:02:43 -0300 Subject: [PATCH 09/10] Version 3.1.0-rc2 --- Split.podspec | 2 +- Split/Common/Utils/Version.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Split.podspec b/Split.podspec index b928c5a7..50114b2d 100644 --- a/Split.podspec +++ b/Split.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Split' s.module_name = 'Split' - s.version = '3.1.0-rc1' + s.version = '3.1.0-rc2' s.summary = 'iOS SDK for Split' s.description = <<-DESC This SDK is designed to work with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. diff --git a/Split/Common/Utils/Version.swift b/Split/Common/Utils/Version.swift index 61d92f48..0d683249 100644 --- a/Split/Common/Utils/Version.swift +++ b/Split/Common/Utils/Version.swift @@ -9,7 +9,7 @@ import Foundation class Version { private static let kSdkPlatform: String = "ios" - private static let kVersion = "3.1.0-rc1" + private static let kVersion = "3.1.0-rc2" static var semantic: String { return kVersion From d4ccae024f8e23e15e10a646ecf87dd57c03a11e Mon Sep 17 00:00:00 2001 From: Gaston Thea Date: Mon, 20 Jan 2025 19:22:46 -0300 Subject: [PATCH 10/10] Prepare release 3.1.0 --- CHANGES.txt | 3 +++ Split.podspec | 2 +- Split/Common/Utils/Version.swift | 2 +- SplitiOSUnit_4.xctestplan | 5 +++++ 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 8f01699b..7b390601 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +3.1.0: (Jan 20, 2025) +- Added support for the new impressions tracking toggle available on feature flags, both respecting the setting and including the new field being returned on SplitView type objects. Read more in our docs. + 3.0.0: (Nov 1, 2024) - Added support for targeting rules based on large segments. - BREAKING: Dropped support for Split Proxy below version 5.9.0. The SDK now requires Split Proxy 5.9.0 or above. diff --git a/Split.podspec b/Split.podspec index 50114b2d..dc92eaed 100644 --- a/Split.podspec +++ b/Split.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'Split' s.module_name = 'Split' - s.version = '3.1.0-rc2' + s.version = '3.1.0' s.summary = 'iOS SDK for Split' s.description = <<-DESC This SDK is designed to work with Split, the platform for controlled rollouts, serving features to your users via the Split feature flag to manage your complete customer experience. diff --git a/Split/Common/Utils/Version.swift b/Split/Common/Utils/Version.swift index 0d683249..298d3c38 100644 --- a/Split/Common/Utils/Version.swift +++ b/Split/Common/Utils/Version.swift @@ -9,7 +9,7 @@ import Foundation class Version { private static let kSdkPlatform: String = "ios" - private static let kVersion = "3.1.0-rc2" + private static let kVersion = "3.1.0" static var semantic: String { return kVersion diff --git a/SplitiOSUnit_4.xctestplan b/SplitiOSUnit_4.xctestplan index fe28c302..2f54c09d 100644 --- a/SplitiOSUnit_4.xctestplan +++ b/SplitiOSUnit_4.xctestplan @@ -92,6 +92,11 @@ "ImpressionsObserverTest", "ImpressionsRecorderWorkerTests", "ImpressionsStorageTest", + "ImpressionsToggleTest", + "ImpressionsToggleTest\/testDebugMode()", + "ImpressionsToggleTest\/testManagerContainsProperty()", + "ImpressionsToggleTest\/testNoneMode()", + "ImpressionsToggleTest\/testOptimizedMode()", "ImpressionsTrackerTest", "InListSemverMatcherTest", "InMemoryTelemetryStorageTest",