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
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ let package = Package(
targets: ["Lockman"])
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.22.2"),
.package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"602.0.0"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.22.3"),
.package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"603.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.3"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.5"),
Expand Down
4 changes: 2 additions & 2 deletions [email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ let package = Package(
targets: ["Lockman"])
],
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.22.2"),
.package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"602.0.0"),
.package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.22.3"),
.package(url: "https://github.com/swiftlang/swift-syntax", "509.0.0"..<"603.0.0"),
.package(url: "https://github.com/pointfreeco/swift-macro-testing", from: "0.6.3"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.4.5"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import ComposableArchitecture
public enum LockmanComposableIssueReporter: LockmanIssueReporter {
public static func reportIssue(
_ message: String,
file: StaticString = #file,
file: StaticString = #fileID,
line: UInt = #line
) {
IssueReporting.reportIssue(message, fileID: file, line: line)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Lockman/Core/Protocols/LockmanIssueReporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public protocol LockmanIssueReporter {
public enum LockmanDefaultIssueReporter: LockmanIssueReporter {
public static func reportIssue(
_ message: String,
file: StaticString = #file,
file: StaticString = #filePath,
line: UInt = #line
) {
#if DEBUG
Expand All @@ -49,7 +49,7 @@ public enum LockmanIssueReporting {
/// Reports an issue using the configured reporter.
public static func reportIssue(
_ message: String,
file: StaticString = #file,
file: StaticString = #filePath,
line: UInt = #line
) {
reporter.reportIssue(message, file: file, line: line)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import XCTest

@testable import Lockman

// ✅ IMPLEMENTED: Comprehensive LockmanResult enum tests with 3-phase approach
// ✅ 13 test methods covering all enum cases, protocol conformance, and integration scenarios
// ✅ Phase 1: Basic enum case testing (success, successWithPrecedingCancellation, cancel)
// ✅ UPDATED: Comprehensive LockmanResult enum tests updated for new API with unlockToken
// ✅ 13 test methods covering all enum cases with unlockToken parameter
// ✅ Phase 1: Basic enum case testing (success, successWithPrecedingCancellation, cancel) with unlockToken
// ✅ Phase 2: Sendable conformance and concurrent access testing
// ✅ Phase 3: Error protocol integration, documentation examples, and type system testing
// ✅ Phase 4: unlockToken functionality and type safety testing

final class LockmanResultTests: XCTestCase {

Expand All @@ -20,7 +21,7 @@ final class LockmanResultTests: XCTestCase {
LockmanManager.cleanup.all()
}

// MARK: - Test Error Types for Testing
// MARK: - Test Error Types and Mock Objects for Testing

// Basic error for testing .cancel case
private struct TestBasicError: LockmanError {
Expand All @@ -42,55 +43,69 @@ final class LockmanResultTests: XCTestCase {

init(
message: String, cancelledInfo: any LockmanInfo,
boundaryId: any LockmanBoundaryId = "testBoundary"
boundaryId: any LockmanBoundaryId = TestBoundaryId.test
) {
self.message = message
self.cancelledInfo = cancelledInfo
self.boundary = boundaryId
}
}

// Basic LockmanInfo for testing
private struct TestLockmanInfo: LockmanInfo {
let strategyId: LockmanStrategyId
let actionId: LockmanActionId
let uniqueId: UUID

init(strategyId: LockmanStrategyId = "TestStrategy", actionId: LockmanActionId = "testAction") {
self.strategyId = strategyId
self.actionId = actionId
self.uniqueId = UUID()
}

var debugDescription: String {
"TestLockmanInfo(actionId: '\(actionId)')"
}
// Test boundary ID type alias for easier usage
private typealias TestBoundary = TestBoundaryId

// Helper method to create unlock token for testing
private func createTestUnlockToken(
boundaryId: TestBoundary = TestBoundary.test,
info: TestLockmanInfo = TestLockmanInfo(
actionId: "testAction",
strategyId: "TestSingleExecutionStrategy"
)
) -> LockmanUnlock<TestBoundary, TestLockmanInfo> {
let strategy = TestSingleExecutionStrategy()
let anyStrategy = AnyLockmanStrategy(strategy)
return LockmanUnlock(
id: boundaryId,
info: info,
strategy: anyStrategy,
unlockOption: .immediate
)
}

// MARK: - Phase 1: Basic Enum Cases

func testLockmanResultSuccessCase() {
// Test .success case
let result = LockmanResult.success
// Test .success case with unlockToken
let unlockToken = createTestUnlockToken()
let result = LockmanResult<TestBoundary, TestLockmanInfo>.success(unlockToken: unlockToken)

// Test pattern matching
switch result {
case .success:
XCTAssertTrue(true) // Success case matched
case .success(let token):
XCTAssertNotNil(token)
// Verify unlock token exists (info properties are internal)
default:
XCTFail("Should match .success case")
}
}

func testLockmanResultSuccessWithPrecedingCancellationCase() {
// Test .successWithPrecedingCancellation case
let testInfo = TestLockmanInfo(actionId: "testAction")
// Test .successWithPrecedingCancellation case with unlockToken
let testInfo = TestLockmanInfo(actionId: "testAction", strategyId: "TestSingleExecutionStrategy")
let error = TestPrecedingError(message: "Previous operation cancelled", cancelledInfo: testInfo)
let result = LockmanResult.successWithPrecedingCancellation(error: error)
let unlockToken = createTestUnlockToken(info: testInfo)
let result = LockmanResult<TestBoundary, TestLockmanInfo>.successWithPrecedingCancellation(
unlockToken: unlockToken,
error: error
)

// Test pattern matching
switch result {
case .successWithPrecedingCancellation(let precedingError):
case .successWithPrecedingCancellation(let token, let precedingError):
// Test unlock token
XCTAssertNotNil(token)

// Test error
XCTAssertEqual(precedingError.errorDescription, "Previous operation cancelled")
XCTAssertTrue(precedingError is TestPrecedingError)
if let testError = precedingError as? TestPrecedingError {
Expand All @@ -102,9 +117,9 @@ final class LockmanResultTests: XCTestCase {
}

func testLockmanResultCancelCase() {
// Test .cancel case
// Test .cancel case (no unlock token since lock failed)
let error = TestBasicError(message: "Operation cancelled")
let result = LockmanResult.cancel(error)
let result = LockmanResult<TestBoundary, TestLockmanInfo>.cancel(error)

// Test pattern matching
switch result {
Expand All @@ -120,10 +135,14 @@ final class LockmanResultTests: XCTestCase {

func testLockmanResultSendableConformance() async {
// Test Sendable conformance with concurrent access
let testInfo = TestLockmanInfo(actionId: "concurrentTest")
let testInfo = TestLockmanInfo(actionId: "concurrentTest", strategyId: "TestSingleExecutionStrategy")
let precedingError = TestPrecedingError(
message: "Concurrent test error", cancelledInfo: testInfo)
let result = LockmanResult.successWithPrecedingCancellation(error: precedingError)
let unlockToken = createTestUnlockToken(info: testInfo)
let result = LockmanResult<TestBoundary, TestLockmanInfo>.successWithPrecedingCancellation(
unlockToken: unlockToken,
error: precedingError
)

await withTaskGroup(of: String.self) { group in
group.addTask {
Expand Down Expand Up @@ -165,7 +184,7 @@ final class LockmanResultTests: XCTestCase {
func testLockmanResultWithLockmanErrorProtocol() {
// Test with different LockmanError implementations
let basicError = TestBasicError(message: "Basic error test")
let cancelResult = LockmanResult.cancel(basicError)
let cancelResult = LockmanResult<TestBoundary, TestLockmanInfo>.cancel(basicError)

switch cancelResult {
case .cancel(let error):
Expand All @@ -179,13 +198,20 @@ final class LockmanResultTests: XCTestCase {

func testLockmanResultWithPrecedingCancellationErrorProtocol() {
// Test with LockmanPrecedingCancellationError protocol
let testInfo = TestLockmanInfo(actionId: "protocolTest")
let testInfo = TestLockmanInfo(actionId: "protocolTest", strategyId: "TestSingleExecutionStrategy")
let precedingError = TestPrecedingError(
message: "Preceding error test", cancelledInfo: testInfo)
let result = LockmanResult.successWithPrecedingCancellation(error: precedingError)
let unlockToken = createTestUnlockToken(info: testInfo)
let result = LockmanResult<TestBoundary, TestLockmanInfo>.successWithPrecedingCancellation(
unlockToken: unlockToken,
error: precedingError
)

switch result {
case .successWithPrecedingCancellation(let error):
case .successWithPrecedingCancellation(let token, let error):
// Test unlock token
XCTAssertNotNil(token)

// Test LockmanPrecedingCancellationError protocol conformance
XCTAssertTrue(error is any LockmanPrecedingCancellationError)
XCTAssertTrue(error is any LockmanError) // Should also conform to base protocol
Expand All @@ -200,11 +226,13 @@ final class LockmanResultTests: XCTestCase {

func testLockmanResultExhaustivePatternMatching() {
// Test all cases in a single comprehensive function
let testInfo = TestLockmanInfo(actionId: "comprehensiveTest")
let testInfo = TestLockmanInfo(actionId: "comprehensiveTest", strategyId: "TestSingleExecutionStrategy")
let unlockToken = createTestUnlockToken(info: testInfo)

let results: [LockmanResult] = [
.success,
let results: [LockmanResult<TestBoundary, TestLockmanInfo>] = [
.success(unlockToken: createTestUnlockToken()),
.successWithPrecedingCancellation(
unlockToken: unlockToken,
error: TestPrecedingError(
message: "Comprehensive preceding error",
cancelledInfo: testInfo
Expand All @@ -214,11 +242,13 @@ final class LockmanResultTests: XCTestCase {

for (index, result) in results.enumerated() {
switch result {
case .success:
case .success(let token):
XCTAssertEqual(index, 0, "Success should be first result")
XCTAssertNotNil(token)

case .successWithPrecedingCancellation(let error):
case .successWithPrecedingCancellation(let token, let error):
XCTAssertEqual(index, 1, "SuccessWithPrecedingCancellation should be second result")
XCTAssertNotNil(token)
XCTAssertNotNil(error.errorDescription)
XCTAssertEqual(error.lockmanInfo.actionId, "comprehensiveTest")

Expand All @@ -231,14 +261,14 @@ final class LockmanResultTests: XCTestCase {

func testLockmanResultNestedSwitchHandling() {
// Test nested pattern matching scenarios
let testInfo = TestLockmanInfo(actionId: "nestedTest")
let testInfo = TestLockmanInfo(actionId: "nestedTest", strategyId: "TestSingleExecutionStrategy")

func processResult(_ result: LockmanResult) -> String {
func processResult(_ result: LockmanResult<TestBoundary, TestLockmanInfo>) -> String {
switch result {
case .success:
return "proceed_immediately"

case .successWithPrecedingCancellation(let error):
case .successWithPrecedingCancellation(_, let error):
// Test nested logic for preceding cancellation
if error.lockmanInfo.actionId.contains("nested") {
return "cancel_and_proceed_nested"
Expand All @@ -258,13 +288,14 @@ final class LockmanResultTests: XCTestCase {

// Test each case
XCTAssertEqual(
processResult(.success),
processResult(.success(unlockToken: createTestUnlockToken())),
"proceed_immediately"
)

XCTAssertEqual(
processResult(
.successWithPrecedingCancellation(
unlockToken: createTestUnlockToken(info: testInfo),
error: TestPrecedingError(
message: "Nested test",
cancelledInfo: testInfo
Expand All @@ -282,14 +313,15 @@ final class LockmanResultTests: XCTestCase {

func testLockmanResultDocumentationExamples() {
// Test examples that match the documentation
let testInfo = TestLockmanInfo(strategyId: "ExampleStrategy", actionId: "documentationExample")
let testInfo = TestLockmanInfo(actionId: "documentationExample", strategyId: "ExampleStrategy")

// Example 1: Success case (most common)
let successResult = LockmanResult.success
let unlockToken = createTestUnlockToken(info: testInfo)
let successResult = LockmanResult<TestBoundary, TestLockmanInfo>.success(unlockToken: unlockToken)
switch successResult {
case .success:
case .success(let token):
// Operation can proceed immediately without any additional cleanup
XCTAssertTrue(true) // This is the expected path
XCTAssertNotNil(token)
default:
XCTFail("Documentation example should succeed")
}
Expand All @@ -299,13 +331,17 @@ final class LockmanResultTests: XCTestCase {
message: "Lower priority operation cancelled",
cancelledInfo: testInfo
)
let precedingResult = LockmanResult.successWithPrecedingCancellation(error: precedingError)
let precedingResult = LockmanResult<TestBoundary, TestLockmanInfo>.successWithPrecedingCancellation(
unlockToken: createTestUnlockToken(info: testInfo),
error: precedingError
)

switch precedingResult {
case .successWithPrecedingCancellation(let error):
case .successWithPrecedingCancellation(let token, let error):
// 1. Cancel the existing operation (usually via Effect cancellation)
// 2. Immediately unlock the cancelled action to prevent resource leaks
// 3. Proceed with the new operation
XCTAssertNotNil(token)
XCTAssertNotNil(error.lockmanInfo)
XCTAssertEqual(error.lockmanInfo.actionId, "documentationExample")
default:
Expand All @@ -314,7 +350,7 @@ final class LockmanResultTests: XCTestCase {

// Example 3: Cancel case (conflict situations)
let cancelError = TestBasicError(message: "Higher priority operation is already active")
let cancelResult = LockmanResult.cancel(cancelError)
let cancelResult = LockmanResult<TestBoundary, TestLockmanInfo>.cancel(cancelError)

switch cancelResult {
case .cancel(let error):
Expand Down Expand Up @@ -352,10 +388,10 @@ final class LockmanResultTests: XCTestCase {
var boundaryId: any LockmanBoundaryId { boundary }
}

let testInfo = TestLockmanInfo(actionId: "realErrorTest")
let testInfo = TestLockmanInfo(actionId: "realErrorTest", strategyId: "TestSingleExecutionStrategy")

// Test with conflict error
let conflictResult = LockmanResult.cancel(
let conflictResult = LockmanResult<TestBoundary, TestLockmanInfo>.cancel(
ConflictError(
conflictingActionId: "existingAction",
boundaryId: "mainBoundary"
Expand All @@ -374,17 +410,22 @@ final class LockmanResultTests: XCTestCase {
}

// Test with priority error
let priorityResult = LockmanResult.successWithPrecedingCancellation(
let priorityResult = LockmanResult<TestBoundary, TestLockmanInfo>.successWithPrecedingCancellation(
unlockToken: createTestUnlockToken(info: testInfo),
error: PriorityError(
cancelledInfo: testInfo,
boundary: "testBoundary",
boundary: TestBoundaryId.test,
newPriority: 10,
oldPriority: 5
)
)

switch priorityResult {
case .successWithPrecedingCancellation(let error):
case .successWithPrecedingCancellation(let token, let error):
// Test unlock token
XCTAssertNotNil(token)

// Test priority error
if let priorityError = error as? PriorityError {
XCTAssertEqual(priorityError.newPriority, 10)
XCTAssertEqual(priorityError.oldPriority, 5)
Expand Down
Loading