Skip to content

Commit

Permalink
Merge branch 'release/5.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
matus-tomlein committed Oct 2, 2023
2 parents 1f76199 + 688d2bc commit 48a0d06
Show file tree
Hide file tree
Showing 17 changed files with 127 additions and 31 deletions.
26 changes: 20 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
runs-on: macos-11
strategy:
matrix:
platform: [ios, osx, tvos, watchos]
platform: [ios, ios, osx, tvos, watchos]
steps:
- uses: actions/checkout@v3
- name: Lint Podspec
Expand All @@ -18,28 +18,39 @@ jobs:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- name: "xcodebuild (iOS 15.2, Xcode 13.2.1)"
- name: "Integration (iOS 15.2, Xcode 13.2.1)"
os: macos-12
xcode-version: "13.2.1"
sdk: iphonesimulator15.2
destination: "platform=iOS Simulator,OS=15.2,name=iPhone 13"
target: IntegrationTests
- name: "Unit (iOS 15.2, Xcode 13.2.1)"
os: macos-12
xcode-version: "13.2.1"
sdk: iphonesimulator15.2
destination: "platform=iOS Simulator,OS=15.2,name=iPhone 13"
- name: "xcodebuild (macOS 12.1, Xcode 13.2.1)"
target: Tests
- name: "Unit (macOS 12.1, Xcode 13.2.1)"
os: macos-12
xcode-version: "13.2.1"
sdk: macosx12.1
destination: "platform=OS X"
- name: "xcodebuild (watchOS 8.3, Xcode 13.2.1)"
target: Tests
- name: "Unit (watchOS 8.3, Xcode 13.2.1)"
os: macos-12
xcode-version: "13.2.1"
sdk: watchos8.3
destination: "platform=watchOS Simulator,OS=8.3,name=Apple Watch Series 7 - 45mm"
- name: "xcodebuild (tvOS 15.2, Xcode 13.2.1)"
target: Tests
- name: "Unit (tvOS 15.2, Xcode 13.2.1)"
os: macos-12
xcode-version: "13.2.1"
sdk: appletvsimulator15.2
destination: "platform=tvOS Simulator,OS=15.2,name=Apple TV"
target: Tests
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -53,13 +64,15 @@ jobs:
key: ${{ runner.os }}-micro

- name: Get micro
if: steps.cache-micro.outputs.cache-hit != 'true'
if: steps.cache-micro.outputs.cache-hit != 'true' && matrix.target == 'IntegrationTests'
run: curl -o micro.jar -L https:/snowplow-incubator/snowplow-micro/releases/download/micro-1.7.0/snowplow-micro-1.7.0.jar

- name: Run Micro in background
if: matrix.target == 'IntegrationTests'
run: java -jar micro.jar &

- name: Wait on Micro endpoint
if: matrix.target == 'IntegrationTests'
timeout-minutes: 2
run: while ! nc -z '0.0.0.0' 9090; do sleep 1; done
# -- Micro --
Expand All @@ -73,6 +86,7 @@ jobs:
-scheme SnowplowTracker \
-sdk "${{ matrix.sdk }}" \
-destination "${{ matrix.destination }}" \
-only-testing ${{ matrix.target }} \
clean test | xcpretty
build_objc_demo_app:
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Version 5.5.0 (2023-10-02)
--------------------------
Add option to disable retrying any failed requests (#826)
Separate targets for unit and integration tests (#831)

Version 5.4.2 (2023-08-08)
--------------------------
Update vendor and version of the player schema used in media tracking (#812)
Expand Down
2 changes: 1 addition & 1 deletion Examples
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ class TestTrackEventsToMicro: XCTestCase {

override func setUp() {
super.setUp()

let trackerConfig = TrackerConfiguration()
.logLevel(.debug)

tracker = Snowplow.createTracker(namespace: "testMicro",
network: NetworkConfiguration(endpoint: Micro.endpoint))!
tracker = Snowplow.createTracker(namespace: "testMicro-" + UUID().uuidString,
network: NetworkConfiguration(endpoint: Micro.endpoint),
configurations: [trackerConfig])!

Micro.setUpMockerIgnores()
wait(for: [Micro.reset()], timeout: Micro.timeout)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import Foundation
import XCTest
import Mocker

class Micro {

Expand All @@ -26,14 +25,6 @@ class Micro {
static let endpoint = "http://0.0.0.0:9090"
#endif

class func setUpMockerIgnores() {
Mocker.ignore(URL(string: "\(endpoint)/micro/good")!)
Mocker.ignore(URL(string: "\(endpoint)/micro/reset")!)
Mocker.ignore(URL(string: "\(endpoint)/micro/all")!)
Mocker.ignore(URL(string: "\(endpoint)/com.snowplowanalytics.snowplow/tp2")!)
Mocker.ignore(URL(string: "\(endpoint)/i")!)
}

class func reset() -> XCTestExpectation {
let expectation = XCTestExpectation(description: "Reset Micro")
let url = URLRequest(url: URL(string: "\(endpoint)/micro/reset")!)
Expand Down
8 changes: 7 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ let package = Package(
"SnowplowTracker",
"Mocker"
],
path: "Tests")
path: "Tests"),
.testTarget(
name: "IntegrationTests",
dependencies: [
"SnowplowTracker"
],
path: "IntegrationTests")
]
)
#if swift(>=5.6)
Expand Down
2 changes: 1 addition & 1 deletion SnowplowTracker.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "SnowplowTracker"
s.version = "5.4.2"
s.version = "5.5.0"
s.summary = "Snowplow event tracker for iOS, macOS, tvOS, watchOS for apps and games."
s.description = <<-DESC
Snowplow is a mobile and event analytics platform with a difference: rather than tell our users how they should analyze their data, we deliver their event-level data in their own data warehouse, on their own Amazon Redshift or Postgres database, so they can analyze it any way they choose. Snowplow mobile is used by data-savvy games companies and app developers to better understand their users and how they engage with their games and applications. Snowplow is open source using the business-friendly Apache License, Version 2.0 and scales horizontally to many billions of events.
Expand Down
5 changes: 4 additions & 1 deletion Sources/Core/Emitter/Emitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,9 @@ class Emitter: NSObject, EmitterEventProcessing {
_customRetryForStatusCodes = customRetryForStatusCodes ?? [:]
}
}

/// Whether retrying failed requests is allowed
var retryFailedRequests: Bool = EmitterDefaults.retryFailedRequests

/// Returns the number of events in the DB.
var dbCount: Int {
Expand Down Expand Up @@ -396,7 +399,7 @@ class Emitter: NSObject, EmitterEventProcessing {
if let array = resultIndexArray {
removableEvents.append(contentsOf: array)
}
} else if result.shouldRetry(customRetryForStatusCodes) {
} else if result.shouldRetry(customRetryForStatusCodes, retryAllowed: retryFailedRequests) {
failedWillRetryCount += resultIndexArray?.count ?? 0
} else {
failedWontRetryCount += resultIndexArray?.count ?? 0
Expand Down
10 changes: 10 additions & 0 deletions Sources/Core/Emitter/EmitterControllerImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ class EmitterControllerImpl: Controller, EmitterController {
emitter.customRetryForStatusCodes = newValue
}
}

var retryFailedRequests: Bool {
get {
return emitter.retryFailedRequests
}
set {
dirtyConfig.retryFailedRequests = newValue
emitter.retryFailedRequests = newValue
}
}

// MARK: - Methods

Expand Down
1 change: 1 addition & 0 deletions Sources/Core/Tracker/ServiceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ class ServiceProvider: NSObject, ServiceProviderProtocol {
emitter.eventStore = self.emitterConfiguration.eventStore
emitter.callback = self.emitterConfiguration.requestCallback
emitter.customRetryForStatusCodes = self.emitterConfiguration.customRetryForStatusCodes
emitter.retryFailedRequests = self.emitterConfiguration.retryFailedRequests
}

let emitter: Emitter
Expand Down
2 changes: 1 addition & 1 deletion Sources/Core/TrackerConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import Foundation

// --- Version
let kSPRawVersion = "5.4.2"
let kSPRawVersion = "5.5.0"
#if os(iOS)
let kSPVersion = "ios-\(kSPRawVersion)"
#elseif os(tvOS)
Expand Down
27 changes: 27 additions & 0 deletions Sources/Snowplow/Configurations/EmitterConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ public protocol EmitterConfigurationProtocol: AnyObject {
/// Whether to anonymise server-side user identifiers including the `network_userid` and `user_ipaddress`
@objc
var serverAnonymisation: Bool { get set }
/// Whether to retry sending events that failed to be sent to the collector.
/// If disabled, events that failed to be sent will be dropped regardless of other configuration (such as the customRetryForStatusCodes).
@objc
var retryFailedRequests: Bool { get set }
}

/// It allows the tracker configuration from the emission perspective.
Expand Down Expand Up @@ -123,6 +127,15 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
set { _eventStore = newValue }
}

private var _retryFailedRequests: Bool?
/// Whether to retry sending events that failed to be sent to the collector.
/// If disabled, events that failed to be sent will be dropped regardless of other configuration (such as the customRetryForStatusCodes).
@objc
public var retryFailedRequests: Bool {
get { return _retryFailedRequests ?? sourceConfig?.retryFailedRequests ?? EmitterDefaults.retryFailedRequests }
set { _retryFailedRequests = newValue }
}

// MARK: - Internal

/// Fallback configuration to read from in case requested values are not present in this configuraiton.
Expand Down Expand Up @@ -165,6 +178,7 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
)
}
self._serverAnonymisation = dictionary["serverAnonymisation"] as? Bool
self._retryFailedRequests = dictionary["retryFailedRequests"] as? Bool
}


Expand Down Expand Up @@ -236,6 +250,14 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
return self
}

/// Whether to retry sending events that failed to be sent to the collector.
/// If disabled, events that failed to be sent will be dropped regardless of other configuration (such as the customRetryForStatusCodes).
@objc
public func retryFailedRequests(_ retryFailedRequests: Bool) -> Self {
self.retryFailedRequests = retryFailedRequests
return self
}

// MARK: - NSCopying

@objc
Expand All @@ -250,6 +272,7 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
copy.customRetryForStatusCodes = customRetryForStatusCodes
copy.serverAnonymisation = serverAnonymisation
copy.eventStore = eventStore
copy.retryFailedRequests = retryFailedRequests
return copy
}

Expand All @@ -267,6 +290,7 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
coder.encode(byteLimitPost, forKey: "byteLimitPost")
coder.encode(customRetryForStatusCodes, forKey: "customRetryForStatusCodes")
coder.encode(serverAnonymisation, forKey: "serverAnonymisation")
coder.encode(retryFailedRequests, forKey: "retryFailedRequests")
}

required init?(coder: NSCoder) {
Expand All @@ -282,5 +306,8 @@ public class EmitterConfiguration: SerializableConfiguration, EmitterConfigurati
customRetryForStatusCodes = retryCodes
}
serverAnonymisation = coder.decodeBool(forKey: "serverAnonymisation")
if coder.containsValue(forKey: "retryFailedRequests") {
retryFailedRequests = coder.decodeBool(forKey: "retryFailedRequests")
}
}
}
1 change: 1 addition & 0 deletions Sources/Snowplow/Emitter/EmitterDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ public class EmitterDefaults {
public private(set) static var byteLimitPost = 40000
public private(set) static var serverAnonymisation = false
public private(set) static var bufferOption: BufferOption = .defaultGroup
public private(set) static var retryFailedRequests = true
}
7 changes: 6 additions & 1 deletion Sources/Snowplow/Network/RequestResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ public class RequestResult: NSObject {

/// - Parameter customRetryForStatusCodes: mapping of custom retry rules for HTTP status codes in Collector response.
/// - Returns: Whether sending the events to the Collector should be retried.
func shouldRetry(_ customRetryForStatusCodes: [Int : Bool]?) -> Bool {
func shouldRetry(_ customRetryForStatusCodes: [Int : Bool]?, retryAllowed: Bool) -> Bool {
// don't retry if successful
if isSuccessful {
return false
}

// don't retry if retries are not allowed
if !retryAllowed {
return false
}

// don't retry if request is larger than max byte limit
if isOversize {
Expand Down
24 changes: 24 additions & 0 deletions Tests/Legacy Tests/LegacyTestEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,30 @@ class LegacyTestEmitter: XCTestCase {

emitter.flush()
}

func testDoesNotRetryFailedRequestsIfDisabled() {
let networkConnection = MockNetworkConnection(requestOption: .get, statusCode: 500)
let emitter = self.emitter(with: networkConnection, bufferOption: .single)
emitter.retryFailedRequests = false

emitter.addPayload(toBuffer: generatePayloads(1).first!)

Thread.sleep(forTimeInterval: 1)

// no events in queue since they were dropped because retrying is disabled
XCTAssertEqual(0, emitter.dbCount)

emitter.retryFailedRequests = true

emitter.addPayload(toBuffer: generatePayloads(1).first!)

Thread.sleep(forTimeInterval: 1)

// event still in queue because retrying is enabled
XCTAssertEqual(1, emitter.dbCount)

emitter.flush()
}

// MARK: - Emitter builder

Expand Down
18 changes: 12 additions & 6 deletions Tests/TestRequestResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TestRequestResult: XCTestCase {

XCTAssertNotNil(result)
XCTAssertEqual(result.isSuccessful, true)
XCTAssertEqual(result.shouldRetry([:]), false)
XCTAssertEqual(result.shouldRetry([:], retryAllowed: true), false)
XCTAssertEqual(result.storeIds, emitterEventIds)
}

Expand All @@ -39,7 +39,7 @@ class TestRequestResult: XCTestCase {
emitterEventIds?.append(1)
let result = RequestResult(statusCode: 500, oversize: false, storeIds: emitterEventIds)
XCTAssertEqual(result.isSuccessful, false)
XCTAssertEqual(result.shouldRetry([:]), true)
XCTAssertEqual(result.shouldRetry([:], retryAllowed: true), true)
}

func testDefaultResult() {
Expand All @@ -53,13 +53,13 @@ class TestRequestResult: XCTestCase {
func testOversizedFailedRequest() {
let result = RequestResult(statusCode: 500, oversize: true, storeIds: [])
XCTAssertEqual(result.isSuccessful, false)
XCTAssertEqual(result.shouldRetry([:]), false)
XCTAssertEqual(result.shouldRetry([:], retryAllowed: true), false)
}

func testFailedRequestWithNoRetryStatus() {
let result = RequestResult(statusCode: 403, oversize: false, storeIds: [])
XCTAssertEqual(result.isSuccessful, false)
XCTAssertEqual(result.shouldRetry([:]), false)
XCTAssertEqual(result.shouldRetry([:], retryAllowed: true), false)
}

func testFailedRequestWithCustomNoRetryStatus() {
Expand All @@ -68,9 +68,15 @@ class TestRequestResult: XCTestCase {
customRetryRules[500] = false

var result = RequestResult(statusCode: 403, oversize: false, storeIds: [])
XCTAssertEqual(result.shouldRetry(customRetryRules), true)
XCTAssertEqual(result.shouldRetry(customRetryRules, retryAllowed: true), true)

result = RequestResult(statusCode: 500, oversize: false, storeIds: [])
XCTAssertEqual(result.shouldRetry(customRetryRules), false)
XCTAssertEqual(result.shouldRetry(customRetryRules, retryAllowed: true), false)
}

func testFailedRequestWithDisabledRetry() {
let result = RequestResult(statusCode: 500, oversize: false, storeIds: [])
XCTAssertFalse(result.isSuccessful)
XCTAssertFalse(result.shouldRetry(nil, retryAllowed: false))
}
}
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.4.2
5.5.0

0 comments on commit 48a0d06

Please sign in to comment.