Skip to content

Commit

Permalink
Backfill some tests for requirement
Browse files Browse the repository at this point in the history
Support async, but not yet polling.
  • Loading branch information
younata committed Dec 12, 2023
1 parent 8a4a912 commit 5824149
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 31 deletions.
4 changes: 2 additions & 2 deletions Nimble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -451,16 +451,16 @@
1FD8CD041968AB07008ED995 /* Adapters */,
892FDF1229D3EA7700523A80 /* AsyncExpression.swift */,
1FD8CD081968AB07008ED995 /* DSL.swift */,
891729D62B18431D005CC866 /* Requirement.swift */,
891729D42B1842D6005CC866 /* DSL+Require.swift */,
899441F32902EF0900C1FAF9 /* DSL+AsyncAwait.swift */,
891729D42B1842D6005CC866 /* DSL+Require.swift */,
DA9E8C811A414BB9002633C2 /* DSL+Wait.swift */,
1FD8CD091968AB07008ED995 /* Expectation.swift */,
1FD8CD0A1968AB07008ED995 /* Expression.swift */,
1FE661561E6574E20035F243 /* ExpectationMessage.swift */,
1FD8CD0B1968AB07008ED995 /* FailureMessage.swift */,
1F1871E31CA89FB600A34BF2 /* Polling.swift */,
89F5E0852908E655001F9377 /* Polling+AsyncAwait.swift */,
891729D62B18431D005CC866 /* Requirement.swift */,
1F1A742D1940169200FFFC47 /* Info.plist */,
1FD8CD0C1968AB07008ED995 /* Matchers */,
1F1A742E1940169200FFFC47 /* Nimble.h */,
Expand Down
91 changes: 87 additions & 4 deletions Sources/Nimble/DSL+Require.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// Make a ``Requirement`` on a given actual value. The value given is lazily evaluated.
/// Make a ``SyncRequirement`` on a given actual value. The value given is lazily evaluated.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () throws -> T?) -> SyncRequirement<T> {
return SyncRequirement(
expression: Expression(
Expand All @@ -7,7 +7,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, _ expressio
isClosure: true))
}

/// Make a ``Requirement`` on a given actual value. The closure is lazily invoked.
/// Make a ``SyncRequirement`` on a given actual value. The closure is lazily invoked.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T)) -> SyncRequirement<T> {
return SyncRequirement(
expression: Expression(
Expand All @@ -16,7 +16,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, _ expressio
isClosure: true))
}

/// Make a ``Requirement`` on a given actual value. The closure is lazily invoked.
/// Make a ``SyncRequirement`` on a given actual value. The closure is lazily invoked.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) -> SyncRequirement<T> {
return SyncRequirement(
expression: Expression(
Expand All @@ -25,7 +25,7 @@ public func require<T>(file: FileString = #file, line: UInt = #line, _ expressio
isClosure: true))
}

/// Make a ``Requirement`` on a given actual value. The closure is lazily invoked.
/// Make a ``SyncRequirement`` on a given actual value. The closure is lazily invoked.
public func require(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> Void)) -> SyncRequirement<Void> {
return SyncRequirement(
expression: Expression(
Expand All @@ -34,6 +34,63 @@ public func require(file: FileString = #file, line: UInt = #line, _ expression:
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The value given is lazily evaluated.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression,
location: SourceLocation(file: file, line: line),
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The closure is lazily invoked.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T)) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The closure is lazily invoked.
public func require<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The value given is lazily evaluated.
/// This is provided to avoid confusion between `require -> SyncRequirement` and `erequire -> AsyncRequirement`.
public func requireAsync<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression,
location: SourceLocation(file: file, line: line),
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The closure is lazily invoked.
/// This is provided to avoid confusion between `require -> SyncRequirement` and `require -> AsyncRequirement`
public func requireAsync<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T)) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
isClosure: true))
}

/// Make an ``AsyncRequirement`` on a given actual value. The closure is lazily invoked.
/// This is provided to avoid confusion between `require -> SyncRequirement` and `require -> AsyncRequirement`
public func requireAsync<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async -> AsyncRequirement<T> {
return AsyncRequirement(
expression: AsyncExpression(
expression: expression(),
location: SourceLocation(file: file, line: line),
isClosure: true))
}

// MARK: - Unwrap

/// Makes sure that the expression evaluates to a non-nil value, otherwise throw an error.
Expand All @@ -48,3 +105,29 @@ public func unwrap<T>(file: FileString = #file, line: UInt = #line, _ expression
public func unwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() throws -> T?)) throws -> T {
try require(file: file, line: line, expression()).toNot(beNil())
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requireAsync(expression).toNot(beNil())`
public func unwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: @escaping () async throws -> T?) async throws -> T {
try await requireAsync(file: file, line: line, try await expression()).toNot(beNil())
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requireAsync(expression).toNot(beNil())`
@discardableResult
public func unwrap<T>(file: FileString = #file, line: UInt = #line, _ expression: () -> (() async throws -> T?)) async throws -> T {
try await requireAsync(file: file, line: line, expression()).toNot(beNil())
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requireAsync(expression).toNot(beNil())`
public func unwrapAsync<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure @escaping () async throws -> T?) async throws -> T {
try await requireAsync(file: file, line: line, try await expression()).toNot(beNil())
}

/// Makes sure that the async expression evaluates to a non-nil value, otherwise throw an error.
/// As you can tell, this is a much less verbose equivalent to `requireAsync(expression).toNot(beNil())`
@discardableResult
public func unwrapAsync<T>(file: FileString = #file, line: UInt = #line, _ expression: @autoclosure () -> (() async throws -> T?)) async throws -> T {
try await requireAsync(file: file, line: line, expression()).toNot(beNil())
}
77 changes: 59 additions & 18 deletions Sources/Nimble/Requirement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,44 +73,31 @@ internal func executeRequire<T>(_ expression: AsyncExpression<T>, _ style: Expec
}
}

import XCTest

public struct SyncRequirement<Value> {
public let expression: Expression<Value>
public let status: ExpectationStatus

public var location: SourceLocation { expression.location }

private init(expression: Expression<Value>, status: ExpectationStatus) {
self.expression = expression
self.status = status
}

public init(expression: Expression<Value>) {
self.init(expression: expression, status: .pending)
self.expression = expression
}

@discardableResult
public func verify(_ pass: Bool, _ message: FailureMessage, _ value: Value?) throws -> Value {
let handler = NimbleEnvironment.activeInstance.assertionHandler
handler.require(pass, message: message, location: expression.location)
guard pass, let value else {
throw RequirementError(message: message.stringValue, location: self.location)
}
return value

// return try value.get()
}

/// Tests the actual value using a matcher to match.
@discardableResult
public func to(_ matcher: Matcher<Value>, description: String? = nil) throws -> Value {
let (pass, msg, result) = executeRequire(expression, .toMatch, matcher, to: "to", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
@discardableResult
public func toNot(_ matcher: Matcher<Value>, description: String? = nil) throws -> Value {
let (pass, msg, result) = executeRequire(expression, .toNotMatch, matcher, to: "to not", description: description)
return try verify(pass, msg, result)
Expand All @@ -119,21 +106,18 @@ public struct SyncRequirement<Value> {
/// Tests the actual value using a matcher to not match.
///
/// Alias to toNot().
@discardableResult
public func notTo(_ matcher: Matcher<Value>, description: String? = nil) throws -> Value {
try toNot(matcher, description: description)
}

// MARK: - AsyncMatchers
/// Tests the actual value using a matcher to match.
@discardableResult
public func to(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = await executeRequire(expression.toAsyncExpression(), .toMatch, matcher, to: "to", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
@discardableResult
public func toNot(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = await executeRequire(expression.toAsyncExpression(), .toNotMatch, matcher, to: "to not", description: description)
return try verify(pass, msg, result)
Expand All @@ -142,7 +126,64 @@ public struct SyncRequirement<Value> {
/// Tests the actual value using a matcher to not match.
///
/// Alias to toNot().
@discardableResult
public func notTo(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
try await toNot(matcher, description: description)
}
}

public struct AsyncRequirement<Value> {
public let expression: AsyncExpression<Value>

public var location: SourceLocation { expression.location }

public init(expression: AsyncExpression<Value>) {
self.expression = expression
}

public func verify(_ pass: Bool, _ message: FailureMessage, _ value: Value?) throws -> Value {
let handler = NimbleEnvironment.activeInstance.assertionHandler
handler.require(pass, message: message, location: expression.location)
guard pass, let value else {
throw RequirementError(message: message.stringValue, location: self.location)
}
return value
}

/// Tests the actual value using a matcher to match.
public func to(_ matcher: Matcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = executeRequire(await expression.toSynchronousExpression(), .toMatch, matcher, to: "to", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
public func toNot(_ matcher: Matcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = executeRequire(await expression.toSynchronousExpression(), .toNotMatch, matcher, to: "to not", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
///
/// Alias to toNot().
public func notTo(_ matcher: Matcher<Value>, description: String? = nil) async throws -> Value {
try await toNot(matcher, description: description)
}

// MARK: - AsyncMatchers
/// Tests the actual value using a matcher to match.
public func to(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = await executeRequire(expression, .toMatch, matcher, to: "to", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
public func toNot(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
let (pass, msg, result) = await executeRequire(expression, .toNotMatch, matcher, to: "to not", description: description)
return try verify(pass, msg, result)
}

/// Tests the actual value using a matcher to not match.
///
/// Alias to toNot().
public func notTo(_ matcher: AsyncMatcher<Value>, description: String? = nil) async throws -> Value {
try await toNot(matcher, description: description)
}
Expand Down
70 changes: 63 additions & 7 deletions Tests/NimbleTests/DSLTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,17 @@ private func throwingAsyncInt() async throws -> Int {
}

final class DSLTest: XCTestCase {
func testExpectAutoclosureNonThrowing() throws {
// MARK: - Expect
func testExpectAutoclosureNonThrowing() {
let _: SyncExpectation<Int> = expect(1)
let _: SyncExpectation<Int> = expect(nonThrowingInt())
}

func testExpectAutoclosureThrowing() throws {
func testExpectAutoclosureThrowing() {
let _: SyncExpectation<Int> = expect(try throwingInt())
}

func testExpectClosure() throws {
func testExpectClosure() {
let _: SyncExpectation<Int> = expect { 1 }
let _: SyncExpectation<Int> = expect { nonThrowingInt() }
let _: SyncExpectation<Int> = expect { try throwingInt() }
Expand All @@ -45,7 +46,7 @@ final class DSLTest: XCTestCase {
let _: SyncExpectation<Void> = expect { () -> Void in return () }
}

func testExpectAsyncClosure() async throws {
func testExpectAsyncClosure() async {
let _: AsyncExpectation<Int> = expect { 1 }
let _: AsyncExpectation<Int> = expect { await nonThrowingAsyncInt() }
let _: AsyncExpectation<Int> = expect { try await throwingAsyncInt() }
Expand All @@ -63,7 +64,7 @@ final class DSLTest: XCTestCase {
let _: AsyncExpectation<Void> = expect { () -> Void in return () }
}

func testExpectCombinedSyncAndAsyncExpects() async throws {
func testExpectCombinedSyncAndAsyncExpects() async {
await expect { await nonThrowingAsyncInt() }.to(equal(1))
await expecta(await nonThrowingAsyncInt()).to(equal(1))
expect(1).to(equal(1))
Expand All @@ -72,12 +73,67 @@ final class DSLTest: XCTestCase {
expects { nonThrowingInt() }.to(equal(1))
}

// MARK: - Require
func testRequireAutoclosureNonThrowing() {
let _: SyncRequirement<Int> = require(1)
let _: SyncRequirement<Int> = require(nonThrowingInt())
}

func testRequireAutoclosureThrowing() {
let _: SyncRequirement<Int> = require(try throwingInt())
}

func testRequireClosure() {
let _: SyncRequirement<Int> = require { 1 }
let _: SyncRequirement<Int> = require { nonThrowingInt() }
let _: SyncRequirement<Int> = require { try throwingInt() }
let _: SyncRequirement<Int> = require { () -> Int in 1 }
let _: SyncRequirement<Int> = require { () -> Int? in 1 }
let _: SyncRequirement<Int> = require { () -> Int? in nil }

let _: SyncRequirement<Void> = require { }
let _: SyncRequirement<Void> = require { () -> Void in }

let _: SyncRequirement<Void> = require { return }
let _: SyncRequirement<Void> = require { () -> Void in return }

let _: SyncRequirement<Void> = require { return () }
let _: SyncRequirement<Void> = require { () -> Void in return () }
}

func testRequireAsyncClosure() async {
let _: AsyncRequirement<Int> = require { 1 }
let _: AsyncRequirement<Int> = require { await nonThrowingAsyncInt() }
let _: AsyncRequirement<Int> = require { try await throwingAsyncInt() }
let _: AsyncRequirement<Int> = require { () -> Int in 1 }
let _: AsyncRequirement<Int> = require { () -> Int? in 1 }
let _: AsyncRequirement<Int> = require { () -> Int? in nil }

let _: AsyncRequirement<Void> = require { }
let _: AsyncRequirement<Void> = require { () -> Void in }

let _: AsyncRequirement<Void> = require { return }
let _: AsyncRequirement<Void> = require { () -> Void in return }

let _: AsyncRequirement<Void> = require { return () }
let _: AsyncRequirement<Void> = require { () -> Void in return () }
}

func testExpectCombinedSyncAndAsyncRequirements() async throws {
_ = try await require { await nonThrowingAsyncInt() }.to(equal(1))
_ = try await requireAsync(await nonThrowingAsyncInt()).to(equal(1))
_ = try require(1).to(equal(1))

_ = try require { nonThrowingInt() }.to(equal(1))
}

func testRequire() throws {
expect { try require(1).to(equal(1)) }.toNot(throwError())
expect { try require(1).to(equal(1)) }.to(equal(1))
expect { try require(3).toNot(beNil()) }.to(equal(3))

let records = gatherExpectations(silently: true) {
do {
try require(1).to(equal(2))
_ = try require(1).to(equal(2))
} catch {
expect(error).to(matchError(RequirementError.self))
}
Expand Down

0 comments on commit 5824149

Please sign in to comment.