Skip to content
Draft
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
21 changes: 12 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.10
// swift-tools-version:6.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import class Foundation.ProcessInfo
Expand All @@ -7,7 +7,9 @@ import PackageDescription
// Workaround: Since we cannot include the flat just as command line options since then it applies to all targets,
// and ONE of our dependencies currently produces one warning, we have to use this workaround to enable it in _our_
// targets when the flag is set. We should remove the dependencies and then enable the flag globally though just by passing it.
var globalSwiftSettings: [SwiftSetting] = []
var globalSwiftSettings: [SwiftSetting] = [
.swiftLanguageMode(.v5),
]

// ==== ----------------------------------------------------------------------------------------------------------------
// MARK: Products
Expand Down Expand Up @@ -91,6 +93,7 @@ var targets: [PackageDescription.Target] = [
/* permissions: needs full network access */
),
dependencies: []

),
.target(
name: "MultiNodeTestKit",
Expand Down Expand Up @@ -167,9 +170,9 @@ var dependencies: [Package.Dependency] = [
// .package(name: "swift-cluster-membership", path: "Packages/swift-cluster-membership"), // FIXME: just work in progress
.package(url: "https://github.com/apple/swift-cluster-membership", branch: "main"),

.package(url: "https://github.com/apple/swift-nio", from: "2.61.1"),
.package(url: "https://github.com/apple/swift-nio-extras", from: "1.20.0"),
.package(url: "https://github.com/apple/swift-nio-ssl", from: "2.25.0"),
.package(url: "https://github.com/apple/swift-nio", from: "2.75.0"),
.package(url: "https://github.com/apple/swift-nio-extras", from: "1.24.0"),
.package(url: "https://github.com/apple/swift-nio-ssl", from: "2.28.0"),

.package(url: "https://github.com/apple/swift-protobuf", from: "1.25.1"),

Expand Down Expand Up @@ -227,10 +230,10 @@ platforms = nil
#else
platforms = [
// we require the 'distributed actor' language and runtime feature:
.iOS(.v16),
.macOS(.v14),
.tvOS(.v16),
.watchOS(.v9),
.iOS(.v18),
.macOS(.v15),
.tvOS(.v18),
.watchOS(.v11),
]
#endif

Expand Down
51 changes: 30 additions & 21 deletions Sources/DistributedActorsTestKit/ActorTestKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import DistributedActorsConcurrencyHelpers
@testable import DistributedCluster
import Foundation
import Logging

import XCTest
import Testing

/// Contains helper functions for testing Actor based code.
/// Due to their asynchronous nature Actors are sometimes tricky to write assertions for,
Expand Down Expand Up @@ -68,7 +67,7 @@ extension ActorTestKit {
public func makeTestProbe<Message: Codable>(
_ naming: _ActorNaming? = nil,
expecting type: Message.Type = Message.self,
file: StaticString = #filePath, line: UInt = #line
sourceLocation: SourceLocation = #_sourceLocation
) -> ActorTestProbe<Message> {
self.makeProbesLock.lock()
defer { self.makeProbesLock.unlock() }
Expand Down Expand Up @@ -102,7 +101,7 @@ extension ActorTestKit {
/// - Hint: Use `fishForMessages` and `fishFor` to filter expectations for specific events.
public func spawnClusterEventStreamTestProbe(
_ naming: _ActorNaming? = nil,
file: String = #filePath, line: UInt = #line, column: UInt = #column
sourceLocation: SourceLocation = #_sourceLocation
) async -> ActorTestProbe<Cluster.Event> {
let eventStream = self.system.cluster.events
let p = self.makeTestProbe(naming ?? _ActorNaming.prefixed(with: "\(ClusterEventStream.self)-subscriberProbe"), expecting: Cluster.Event.self)
Expand All @@ -127,10 +126,10 @@ extension ActorTestKit {
@discardableResult
public func eventually<T>(
within duration: Duration, interval: Duration = .milliseconds(100),
file: StaticString = #filePath, line: UInt = #line, column: UInt = #column,
sourceLocation: SourceLocation = #_sourceLocation,
_ block: () throws -> T
) throws -> T {
let callSite = CallSiteInfo(file: file, line: line, column: column, function: #function)
let callSite = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let deadline = ContinuousClock.Instant.fromNow(duration)

var lastError: Error?
Expand All @@ -156,7 +155,7 @@ extension ActorTestKit {

let error = EventuallyError(callSite, duration, polledTimes, lastError: lastError)
if !ActorTestKit.isInRepeatableContext() {
XCTFail("\(error)", file: callSite.file, line: callSite.line)
Issue.record("\(error)")
}
throw error
}
Expand All @@ -173,10 +172,10 @@ extension ActorTestKit {
@discardableResult
public func eventually<T>(
within duration: Duration, interval: Duration = .milliseconds(100),
file: StaticString = #filePath, line: UInt = #line, column: UInt = #column,
sourceLocation: SourceLocation = #_sourceLocation,
_ block: () async throws -> T
) async throws -> T {
let callSite = CallSiteInfo(file: file, line: line, column: column, function: #function)
let callSite = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let deadline = ContinuousClock.Instant.fromNow(duration)

var lastError: Error?
Expand All @@ -202,7 +201,7 @@ extension ActorTestKit {

let error = EventuallyError(callSite, duration, polledTimes, lastError: lastError)
if !ActorTestKit.isInRepeatableContext() {
XCTFail("\(error)", file: callSite.file, line: callSite.line)
Issue.record("\(error)", sourceLocation: callSite.sourceLocation)
}
throw error
}
Expand Down Expand Up @@ -237,7 +236,7 @@ public struct EventuallyError: Error, CustomStringConvertible, CustomDebugString
}

message += """
No result within \(self.duration.prettyDescription) for block at \(self.callSite.file):\(self.callSite.line). \
No result within \(self.duration.prettyDescription) for block at \(self.callSite.sourceLocation.fileID):\(self.callSite.sourceLocation.line). \
Queried \(self.polledTimes) times, within \(self.duration.prettyDescription). \
\(lastErrorMessage)
"""
Expand All @@ -262,10 +261,10 @@ extension ActorTestKit {
/// Throws an error when the block fails within the specified time amount.
public func assertHolds(
for duration: Duration, interval: Duration = .milliseconds(100),
file: StaticString = #filePath, line: UInt = #line, column: UInt = #column,
sourceLocation: SourceLocation = #_sourceLocation,
_ block: () throws -> Void
) throws {
let callSite = CallSiteInfo(file: file, line: line, column: column, function: #function)
let callSite = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let deadline = ContinuousClock.Instant.fromNow(duration)

var polledTimes = 0
Expand All @@ -278,12 +277,12 @@ extension ActorTestKit {
} catch {
let error = callSite.error(
"""
Failed within \(duration.prettyDescription) for block at \(file):\(line). \
Failed within \(duration.prettyDescription) for block at \(sourceLocation.fileID):\(sourceLocation.line). \
Queried \(polledTimes) times, within \(duration.prettyDescription). \
Error: \(error)
"""
)
XCTFail("\(error)", file: callSite.file, line: callSite.line)
Issue.record("\(error)", sourceLocation: callSite.sourceLocation)
throw error
}
}
Expand All @@ -296,10 +295,10 @@ extension ActorTestKit {
extension ActorTestKit {
// TODO: how to better hide such more nasty assertions?
// TODO: Not optimal since we always do traverseAll rather than follow the Path of the context
public func _assertActorPathOccupied(_ path: String, file: StaticString = #filePath, line: UInt = #line, column: UInt = #column) throws {
public func _assertActorPathOccupied(_ path: String, sourceLocation: SourceLocation = #_sourceLocation) throws {
precondition(!path.contains("#"), "assertion path MUST NOT contain # id section of an unique path.")

let callSiteInfo = CallSiteInfo(file: file, line: line, column: column, function: #function)
let callSiteInfo = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let res: _TraversalResult<_AddressableActorRef> = self.system._traverseAll { _, ref in
if ref.id.path.description == path {
return .accumulateSingle(ref)
Expand Down Expand Up @@ -464,22 +463,32 @@ extension ActorTestKit {
/// testKit.eventually(within: .seconds(3)) {
/// guard ... else { throw testKit.error("failed to extract expected information") }
/// }
public func error(_ message: String? = nil, file: StaticString = #filePath, line: UInt = #line, column: UInt = #column) -> Error {
let callSite = CallSiteInfo(file: file, line: line, column: column, function: #function)
public func error(_ message: String? = nil, sourceLocation: SourceLocation = #_sourceLocation) -> Error {
let callSite = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let fullMessage: String = message ?? "<no message>"
return callSite.error(fullMessage, failTest: false)
}

public func error(_ message: String? = nil, file: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) -> Error {
let sourceLocation = SourceLocation(fileID: file, filePath: filePath, line: line, column: column)
return self.error(message, sourceLocation: sourceLocation)
}

/// Returns a failure with additional callsite information and fails the test.
///
/// Examples:
///
/// guard ... else { throw testKit.fail("failed to extract expected information") }
public func fail(_ message: String? = nil, file: StaticString = #filePath, line: UInt = #line, column: UInt = #column) -> Error {
let callSite = CallSiteInfo(file: file, line: line, column: column, function: #function)
public func fail(_ message: String? = nil, sourceLocation: SourceLocation = #_sourceLocation) -> Error {
let callSite = CallSiteInfo(sourceLocation: sourceLocation, function: #function)
let fullMessage: String = message ?? "<no message>"
return callSite.error(fullMessage, failTest: true)
}

public func fail(_ message: String? = nil, file: String = #fileID, filePath: String = #filePath, line: Int = #line, column: Int = #column) -> Error {
let sourceLocation = SourceLocation(fileID: file, filePath: filePath, line: line, column: column)
return self.fail(message, sourceLocation: sourceLocation)
}
}

// ==== ----------------------------------------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion Sources/DistributedActorsTestKit/ByteBuffer+Testing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Foundation
import DistributedActorsConcurrencyHelpers
import NIO
import NIOFoundationCompat
import XCTest
import Testing

// TODO: probably remove those?

Expand Down
Loading