Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into vijays/make-entities-…
Browse files Browse the repository at this point in the history
…optionally-sendable
  • Loading branch information
vijaysharm committed Nov 14, 2024
2 parents b44167b + 789608f commit 27ed563
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 59 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ jobs:
lucid_tests:
name: Lucid-iOS Tests
runs-on: macos-14
runs-on: macos-15
timeout-minutes: 30
env:
FASTLANE_LOGS: fastlane/test_output
FASTLANE_FRAGILE_LOGS: fastlane/fragile_test_output
GITHUB_ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
FRAGILE_TESTS: LucidTests/APIClientQueueProcessorTests/test_processor_does_attempt_to_process_request_if_already_running_concurrent_request,LucidTests/CoreManagerPropertyTests/test_that_delegate_gets_called_when_observers_are_released,LucidTests/CoreManagerTests/test_continuous_observer_should_receive_all_updates_in_order,LucidTests/CoreManagerTests/test_manager_should_send_entity_update_to_provider_when_entity_is_set,LucidTests/RelationshipControllerTests/test_relationship_controller_should_continuously_send_events_when_first_event_comes_from_continuous_signal,LucidTests/RelationshipControllerTests/test_relationship_controller_should_continuously_send_events_when_first_event_comes_from_once_signal,LucidTests/StoreStackTests/test_should_fail_to_remove_in_remote_store_only_with_memory_store_first,LucidTests/RecoverableStoreTests/test_store_should_overwrite_a_non_empty_recovery_store_with_a_non_empty_main_store_at_init,LucidTests/RecoverableStoreTests/test_store_only_reflects_main_store_in_get_operations
FRAGILE_TESTS: LucidTests/APIClientQueueProcessorTests/test_processor_does_attempt_to_process_request_if_already_running_concurrent_request,LucidTests/CoreManagerPropertyTests/test_that_delegate_gets_called_when_observers_are_released,LucidTests/CoreManagerTests/test_continuous_observer_should_receive_all_updates_in_order,LucidTests/CoreManagerTests/test_manager_should_send_entity_update_to_provider_when_entity_is_set,LucidTests/RelationshipControllerTests/test_relationship_controller_should_continuously_send_events_when_first_event_comes_from_continuous_signal,LucidTests/RelationshipControllerTests/test_relationship_controller_should_continuously_send_events_when_first_event_comes_from_once_signal,LucidTests/StoreStackTests/test_should_fail_to_remove_in_remote_store_only_with_memory_store_first,LucidTests/RecoverableStoreTests/test_store_should_overwrite_a_non_empty_recovery_store_with_a_non_empty_main_store_at_init,LucidTests/RecoverableStoreTests/test_store_only_reflects_main_store_in_get_operations,LucidTests/RecoverableStoreTests/test_store_affects_both_inner_stores_in_remove_operations_async,LucidTests/CoreManagerTests/test_manager_should_send_entity_update_to_provider_when_entity_is_removed,LucidTests/RecoverableStoreTests/test_store_only_reflects_main_store_in_get_operations_asyncLucidTests/BaseStoreTests/test_store_should_set_1000_entities_in_under_1_second,LucidTests/CoreManagerContractTests/test_continuous_obvserver_should_get_filtered_results_matching_entities_that_meet_contract_requirements,LucidTests/CoreManagerContractTests/test_core_manager_get_should_filter_results_when_enity_does_not_meet_contract_requirements,LucidTests/CoreManagerContractTests/test_core_manager_get_should_return_complete_results_when_entity_meets_contract_requirements,LucidTests/CoreManagerContractTests/test_core_manager_get_should_return_empty_results_when_no_entities_meet_contract_requirements_in_remote_store_for_remote_data_source,LucidTests/CoreManagerContractTests/test_core_manager_get_should_return_local_result_when_no_entities_meet_contract_requirements_in_remote_store_for_remote_or_local_data_source,LucidTestsCoreManagerTests/test_manager_should_send_entity_update_to_provider_with_different_query_when_entity_is_not_found

steps:
- name: Clone Project
uses: actions/checkout@v4
Expand All @@ -42,14 +43,14 @@ jobs:

- name: Run Lucid-iOS Tests
run: |
fastlane scan --scheme Lucid-iOS --skip_testing "$FRAGILE_TESTS" --device "iPhone 15" --output_directory $FASTLANE_LOGS --result_bundle true
fastlane scan --scheme Lucid-iOS --skip_testing "$FRAGILE_TESTS" --device "iPhone 16" --output_directory $FASTLANE_LOGS --result_bundle true
# Some tests need to be reworked. Don't forget about them, but don't crash the build either
# https://scribdjira.atlassian.net/browse/IPT-4387
- name: Run Fragile Tests
continue-on-error: true
run: |
fastlane scan --scheme Lucid-iOS --only_testing "$FRAGILE_TESTS" --device "iPhone 15" --output_directory $FASTLANE_FRAGILE_LOGS --result_bundle true
fastlane scan --scheme Lucid-iOS --only_testing "$FRAGILE_TESTS" --device "iPhone 16" --output_directory $FASTLANE_FRAGILE_LOGS --result_bundle true
- name: Bundle Log Files
run: |
Expand Down
2 changes: 1 addition & 1 deletion .xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15.3
16.1
10 changes: 7 additions & 3 deletions Lucid/Utils/BackgroundTaskManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ import Foundation
import UIKit

protocol CoreBackgroundTaskManaging: AnyObject {
func beginBackgroundTask(expirationHandler: (() -> Void)?) -> UIBackgroundTaskIdentifier
func startBackgroundTask(expirationHandler: (@MainActor @Sendable () -> Void)?) -> UIBackgroundTaskIdentifier
func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier)
}

extension UIApplication: CoreBackgroundTaskManaging {}
extension UIApplication: CoreBackgroundTaskManaging {
func startBackgroundTask(expirationHandler: (@MainActor () -> Void)?) -> UIBackgroundTaskIdentifier {
self.beginBackgroundTask(expirationHandler: expirationHandler)
}
}

/// In charge of keeping one background task alive as long as needed.
protocol BackgroundTaskManaging: AnyObject {
Expand Down Expand Up @@ -91,7 +95,7 @@ final class BackgroundTaskManager: BackgroundTaskManaging {
}
RunLoop.main.add(timer, forMode: .default)

_taskID = coreManager.beginBackgroundTask {
_taskID = coreManager.startBackgroundTask {
timer.invalidate()
self.asyncTaskQueue.async {
Logger.log(.warning, "\(BackgroundTaskManager.self): Background task timed out: \(self._taskID)")
Expand Down
6 changes: 3 additions & 3 deletions LucidTests/Doubles/BackgroundTaskManagerSpy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public final class CoreBackgroundTaskManagerSpy: CoreBackgroundTaskManaging {

// MARK: - Records

public private(set) var expirationHandlerRecords = [UIBackgroundTaskIdentifier: (() -> Void)]()
public private(set) var expirationHandlerRecords = [UIBackgroundTaskIdentifier: (@MainActor @Sendable () -> Void)]()
public private(set) var beginBackgroundTaskCallCountRecord = 0

public private(set) var endBackgroundTaskRecords = [UIBackgroundTaskIdentifier]()
Expand All @@ -31,7 +31,7 @@ public final class CoreBackgroundTaskManagerSpy: CoreBackgroundTaskManaging {
// no-op
}

public func beginBackgroundTask(expirationHandler: (() -> Void)?) -> UIBackgroundTaskIdentifier {
public func startBackgroundTask(expirationHandler: (@MainActor @Sendable () -> Void)?) -> UIBackgroundTaskIdentifier {
let identifier = UIBackgroundTaskIdentifier(rawValue: backgroundTaskIDRawValueStub)
if let expirationHandler = expirationHandler {
expirationHandlerRecords[identifier] = expirationHandler
Expand All @@ -40,7 +40,7 @@ public final class CoreBackgroundTaskManagerSpy: CoreBackgroundTaskManaging {
return identifier
}

public func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {
nonisolated public func endBackgroundTask(_ identifier: UIBackgroundTaskIdentifier) {
endBackgroundTaskRecords.append(identifier)
}
}
Expand Down
81 changes: 36 additions & 45 deletions LucidTests/Utils/AsyncTaskQueueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -456,67 +456,35 @@ final class AsyncTaskQueueTests: XCTestCase {
wait(for: [setUpExpectation], timeout: 1)
}

func test_that_it_runs_in_parallel_until_it_finds_a_barrier_then_it_waits_for_barrier_to_end_to_continue() {
func test_that_it_runs_in_parallel_until_it_finds_a_barrier_then_it_waits_for_barrier_to_end_to_continue() async {
let asyncTaskQueue = AsyncTaskQueue(maxConcurrentTasks: 2)

let operation1 = BlockingOperation()
let operation2 = BlockingOperation()
let operation3 = BlockingOperation()
let operation4 = BlockingOperation()

Task(priority: .first) {
do {
// Create a task group to manage all operations
await withThrowingTaskGroup(of: Void.self) { group in
// First concurrent operations
group.addTask {
try await asyncTaskQueue.enqueue(operation: {
await operation1.setUp()
await operation1.perform()
})
} catch {
XCTFail("unexpected error thrown: \(error)")
}
}

Task(priority: .second) {
do {
group.addTask {
try await asyncTaskQueue.enqueue(operation: {
await operation2.setUp()
await operation2.perform()
})
} catch {
XCTFail("unexpected error thrown: \(error)")
}
}

Task(priority: .third) {
do {
try await asyncTaskQueue.enqueueBarrier(operation: { completion in
defer { completion() }

await operation3.setUp()
await operation3.perform()
})
} catch {
XCTFail("unexpected error thrown: \(error)")
}
}

Task(priority: .fourth) {
do {
try await asyncTaskQueue.enqueue(operation: {
await operation4.setUp()
await operation4.perform()
})
} catch {
XCTFail("unexpected error thrown: \(error)")
}
}

let setUpExpectation = expectation(description: "set_up_expectation")

Task { @MainActor in
// Wait for first two operations to be set up
await operation1.waitForSetUp()
await operation2.waitForSetUp()
try? await Task.sleep(nanoseconds: 1000)


// Verify initial state
let operation1HasStarted = await operation1.hasStarted
let operation2HasStarted = await operation2.hasStarted
var operation3HasStarted = await operation3.hasStarted
Expand All @@ -529,10 +497,23 @@ final class AsyncTaskQueueTests: XCTestCase {
XCTAssertFalse(operation4HasStarted)
XCTAssertEqual(runningTasks, 2)

// Complete first two operations
await operation1.resume()
await operation2.resume()

// Add barrier operation
group.addTask {
try await asyncTaskQueue.enqueueBarrier(operation: { completion in
defer { completion() }
await operation3.setUp()
await operation3.perform()
})
}

// Wait for barrier operation to start
await operation3.waitForSetUp()

// Verify state after barrier starts
let operation1HasCompleted = await operation1.hasCompleted
let operation2HasCompleted = await operation2.hasCompleted
operation3HasStarted = await operation3.hasStarted
Expand All @@ -545,9 +526,21 @@ final class AsyncTaskQueueTests: XCTestCase {
XCTAssertFalse(operation4HasStarted)
XCTAssertEqual(runningTasks, 1)

// Complete barrier operation
await operation3.resume()

// Add final concurrent operation
group.addTask {
try await asyncTaskQueue.enqueue(operation: {
await operation4.setUp()
await operation4.perform()
})
}

// Wait for final operation to start
await operation4.waitForSetUp()

// Verify final state
let operation3HasCompleted = await operation3.hasCompleted
operation4HasStarted = await operation4.hasStarted
runningTasks = await asyncTaskQueue.runningTasks
Expand All @@ -558,14 +551,12 @@ final class AsyncTaskQueueTests: XCTestCase {
XCTAssertTrue(operation4HasStarted)
XCTAssertEqual(runningTasks, 1)

setUpExpectation.fulfill()
// Complete final operation
await operation4.resume()
}

wait(for: [setUpExpectation], timeout: 1)
}

func test_that_enqueue_continues_to_work_even_if_previous_operation_threw_an_error() {

let asyncTaskQueue = AsyncTaskQueue(maxConcurrentTasks: 1)

Task(priority: .first) {
Expand Down
6 changes: 3 additions & 3 deletions LucidTests/Utils/BackgroundTaskManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final class BackgroundTaskManagerTests: XCTestCase {
override func setUp() {
super.setUp()
coreManagerSpy = CoreBackgroundTaskManagerSpy()
manager = BackgroundTaskManager(coreManagerSpy, timeout: 0.25)
manager = BackgroundTaskManager(coreManagerSpy, timeout: 0.3)
}

override func tearDown() {
Expand Down Expand Up @@ -87,12 +87,12 @@ final class BackgroundTaskManagerTests: XCTestCase {
let id = manager.start {}
_ = manager.stop(id)

DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
XCTAssertEqual(self.coreManagerSpy.beginBackgroundTaskCallCountRecord, 1)
XCTAssertEqual(self.coreManagerSpy.endBackgroundTaskRecords.count, 1)
expectation.fulfill()
}

waitForExpectations(timeout: 1)
waitForExpectations(timeout: 2)
}
}

0 comments on commit 27ed563

Please sign in to comment.