Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Lint
run: swiftlint lint --quiet
test:
runs-on: macos-15
runs-on: macos-26
environment: default
steps:
- uses: actions/checkout@v4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

// swiftlint:disable file_length
Expand Down
2 changes: 1 addition & 1 deletion Sources/CoreDataRepository/CoreDataRepository+Fetch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

extension CoreDataRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ extension CoreDataRepository {
/// This operation is non-atomic. Each instance may succeed or fail individually.
@inlinable
public func readAtomically<Model: IdentifiedUnmanagedModel>(
_ ids: some Sequence<Model.UnmanagedId>,
_ ids: some Sequence<Model.UnmanagedId> & Sendable,
as _: Model.Type
) async -> Result<[Model], CoreDataError> {
let context = Transaction.current?.context ?? context
Expand All @@ -115,7 +115,7 @@ extension CoreDataRepository {
/// This operation is non-atomic. Each instance may succeed or fail individually.
@inlinable
public func readAtomically<Model: ReadableUnmanagedModel>(
_ items: some Sequence<Model>
_ items: some Sequence<Model> & Sendable
) async -> Result<[Model], CoreDataError> {
let context = Transaction.current?.context ?? context
return await context.performInChild(schedule: .enqueued) { readContext in
Expand All @@ -134,7 +134,7 @@ extension CoreDataRepository {
/// This operation is non-atomic. Each instance may succeed or fail individually.
@inlinable
public func readAtomically<Model: FetchableUnmanagedModel>(
_ managedIds: some Sequence<NSManagedObjectID>,
_ managedIds: some Sequence<NSManagedObjectID> & Sendable,
as _: Model.Type
) async -> Result<[Model], CoreDataError> {
let context = Transaction.current?.context ?? context
Expand All @@ -154,7 +154,7 @@ extension CoreDataRepository {
/// This operation is non-atomic. Each instance may succeed or fail individually.
@inlinable
public func readAtomically<Model: FetchableUnmanagedModel>(
_ managedIdUrls: some Sequence<URL>,
_ managedIdUrls: some Sequence<URL> & Sendable,
as _: Model.Type
) async -> Result<[Model], CoreDataError> {
let context = Transaction.current?.context ?? context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when an aggregate fetch request changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when an aggregate fetch request changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when a count fetch request changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when a count fetch request changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when a fetch request changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// This source code is licensed under the MIT License (MIT) found in the
// LICENSE file in the root directory of this source tree.

import CoreData
@preconcurrency import CoreData
import Foundation

/// Subscription provider that sends updates when a fetch request changes
Expand Down
10 changes: 7 additions & 3 deletions Tests/CoreDataRepositoryTests/AggregateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import Testing

extension CoreDataRepositoryTests {
@Suite
struct AggregateTests: CoreDataTestSuite, @unchecked Sendable {
struct AggregateTests: CoreDataTestSuite, Sendable {
let container: NSPersistentContainer
let repositoryContext: NSManagedObjectContext
let repository: CoreDataRepository

let fetchRequest: NSFetchRequest<ManagedModel_UuidId> = {
nonisolated(unsafe) let fetchRequest: NSFetchRequest<ManagedModel_UuidId> = {
let request = UnmanagedModel_UuidId.managedFetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \ManagedModel_UuidId.int, ascending: true)]
return request
Expand All @@ -34,7 +34,11 @@ extension CoreDataRepositoryTests {
var objectIds = [NSManagedObjectID]()

mutating func extraSetup() async throws {
let (_expectedValues, _objectIds) = try repositoryContext.performAndWait {
let (_expectedValues, _objectIds) = try repositoryContext.performAndWait { [
self,
repositoryContext,
values
] in
let managedMovies = try values
.map {
try ManagedIdUrlModel_UuidId(fetchable: $0)
Expand Down
28 changes: 12 additions & 16 deletions Tests/CoreDataRepositoryTests/BatchRequestTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ import Testing

extension CoreDataRepositoryTests {
@Suite
struct BatchRequestTests: CoreDataTestSuite {
struct BatchRequestTests: CoreDataTestSuite, Sendable {
let container: NSPersistentContainer
let repositoryContext: NSManagedObjectContext
let repository: CoreDataRepository

let values: [[String: Any]] = [
nonisolated(unsafe) let values: [[String: Any]] = [
ManagedIdUrlModel_UuidId.seeded(1).asDict,
ManagedIdUrlModel_UuidId.seeded(2).asDict,
ManagedIdUrlModel_UuidId.seeded(3).asDict,
ManagedIdUrlModel_UuidId.seeded(4).asDict,
ManagedIdUrlModel_UuidId.seeded(5).asDict,
]
let failureInsertMovies: [[String: Any]] = [
nonisolated(unsafe) let failureInsertMovies: [[String: Any]] = [
["id": "A", "title": 1, "releaseDate": "A"],
["id": "B", "title": 2, "releaseDate": "B"],
["id": "C", "title": 3, "releaseDate": "C"],
["id": "D", "title": 4, "releaseDate": "D"],
["id": "E", "title": 5, "releaseDate": "E"],
]
let failureCreateMovies: [[String: Any]] = [
nonisolated(unsafe) let failureCreateMovies: [[String: Any]] = [
["id": UUID(uniform: "A"), "title": "A", "releaseDate": Date()],
["id": UUID(uniform: "A"), "title": "B", "releaseDate": Date()],
["id": UUID(uniform: "A"), "title": "C", "releaseDate": Date()],
Expand Down Expand Up @@ -70,9 +70,8 @@ extension CoreDataRepositoryTests {

@Test(arguments: [false, true])
func insertSuccess(inTransaction: Bool) async throws {
let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest()
try await repositoryContext.perform {
let count = try repositoryContext.count(for: fetchRequest)
let count = try repositoryContext.count(for: ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.")
}

Expand Down Expand Up @@ -101,7 +100,7 @@ extension CoreDataRepositoryTests {
}

try await repositoryContext.perform {
let data = try repositoryContext.fetch(fetchRequest)
let data = try repositoryContext.fetch(ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(
data.map(\.string).sorted(),
["1", "2", "3", "4", "5"],
Expand All @@ -121,9 +120,8 @@ extension CoreDataRepositoryTests {

@Test(arguments: [false, true])
func insertFailure(inTransaction: Bool) async throws {
let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest()
try await repositoryContext.perform {
let count = try repositoryContext.count(for: fetchRequest)
let count = try repositoryContext.count(for: ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.")
}

Expand All @@ -147,16 +145,15 @@ extension CoreDataRepositoryTests {
}

try await repositoryContext.perform {
let data = try repositoryContext.fetch(fetchRequest)
let data = try repositoryContext.fetch(ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(data.map(\.string).sorted(), [], "There should be no inserted values.")
}
}

@Test(arguments: [false, true])
func updateSuccess(inTransaction: Bool) async throws {
let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest()
try await repositoryContext.perform {
let count = try repositoryContext.count(for: fetchRequest)
let count = try repositoryContext.count(for: ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.")

_ = try values
Expand All @@ -183,7 +180,7 @@ extension CoreDataRepositoryTests {
}

try await repositoryContext.perform {
let data = try repositoryContext.fetch(fetchRequest)
let data = try repositoryContext.fetch(ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(
data.map(\.string).sorted(),
["Updated!", "Updated!", "Updated!", "Updated!", "Updated!"],
Expand All @@ -202,9 +199,8 @@ extension CoreDataRepositoryTests {

@Test(arguments: [false, true])
func deleteSuccess(inTransaction: Bool) async throws {
let fetchRequest = ManagedIdUrlModel_UuidId.managedFetchRequest()
try await repositoryContext.perform {
let count = try repositoryContext.count(for: fetchRequest)
let count = try repositoryContext.count(for: ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(count, 0, "Count of objects in CoreData should be zero at the start of each test.")

_ = try values
Expand Down Expand Up @@ -232,7 +228,7 @@ extension CoreDataRepositoryTests {
}

try await repositoryContext.perform {
let data = try repositoryContext.fetch(fetchRequest)
let data = try repositoryContext.fetch(ManagedIdUrlModel_UuidId.managedFetchRequest())
expectNoDifference(data.map(\.string).sorted(), [], "There should be no remaining values.")
}
// Transaction author refuses to be applied when going through a transaction. Need to investigate further.
Expand Down
24 changes: 9 additions & 15 deletions Tests/CoreDataRepositoryTests/CoreDataTestSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,8 @@ extension CoreDataTestSuite {
// empty by default
}

func mapInContext<I, O>(_ input: I, transform: (I) throws -> O) throws -> O {
try repositoryContext.performAndWait {
try transform(input)
}
}

func verify<T>(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable {
repositoryContext.performAndWait {
repositoryContext.performAndWait { [repositoryContext] in
var managed: T.ManagedModel?
do {
managed = try repositoryContext.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item }
Expand All @@ -71,7 +65,7 @@ extension CoreDataTestSuite {
}

func verify<T>(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable {
try repositoryContext.performAndWait {
try repositoryContext.performAndWait { [repositoryContext] in
var _managed: T.ManagedModel?
do {
_managed = try item.readManaged(from: repositoryContext)
Expand All @@ -91,8 +85,8 @@ extension CoreDataTestSuite {
}

func verify(transactionAuthor: String?, timeStamp: Date) throws {
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
try repositoryContext.performAndWait {
try repositoryContext.performAndWait { [repositoryContext] in
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
let historyResult = try #require(repositoryContext.execute(historyRequest) as? NSPersistentHistoryResult)
let history = try #require(historyResult.result as? [NSPersistentHistoryTransaction])
#expect(history.count > 0)
Expand All @@ -103,7 +97,7 @@ extension CoreDataTestSuite {
}

func verifyDoesNotExist<T>(_ item: T) async throws where T: FetchableUnmanagedModel, T: Equatable {
repositoryContext.performAndWait {
repositoryContext.performAndWait { [repositoryContext] in
var _managed: T.ManagedModel?
do {
_managed = try repositoryContext.fetch(T.managedFetchRequest()).first { try T(managed: $0) == item }
Expand All @@ -118,7 +112,7 @@ extension CoreDataTestSuite {
}

func verifyDoesNotExist<T>(_ item: T) async throws where T: ReadableUnmanagedModel, T: Equatable {
repositoryContext.performAndWait {
repositoryContext.performAndWait { [repositoryContext] in
var _managed: T.ManagedModel?
do {
_managed = try item.readManaged(from: repositoryContext)
Expand All @@ -133,8 +127,8 @@ extension CoreDataTestSuite {
}

func verifyDoesNotExist(transactionAuthor: String?, timeStamp: Date) throws {
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
try repositoryContext.performAndWait {
try repositoryContext.performAndWait { [repositoryContext] in
let historyRequest = NSPersistentHistoryChangeRequest.fetchHistory(after: timeStamp)
let historyResult = try #require(repositoryContext.execute(historyRequest) as? NSPersistentHistoryResult)
let history = try #require(historyResult.result as? [NSPersistentHistoryTransaction])
if transactionAuthor == nil {
Expand All @@ -148,7 +142,7 @@ extension CoreDataTestSuite {
}

func delete(managedId: NSManagedObjectID) throws {
try repositoryContext.performAndWait {
try repositoryContext.performAndWait { [repositoryContext] in
let managed = repositoryContext.object(with: managedId)
repositoryContext.delete(managed)
try repositoryContext.save()
Expand Down
Loading