Skip to content

Commit

Permalink
Merge pull request #175 from fwcd/swift-testing
Browse files Browse the repository at this point in the history
Migrate unit tests to swift-testing
  • Loading branch information
fwcd authored Oct 20, 2024
2 parents 31c9b2c + aef5e6c commit b5a46a2
Show file tree
Hide file tree
Showing 26 changed files with 350 additions and 403 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ let package = Package(
name: "D2ScriptTests",
dependencies: [
.product(name: "Utils", package: "swift-utils"),
.target(name: "D2TestUtils"),
.target(name: "D2Script"),
]
),
Expand Down
2 changes: 1 addition & 1 deletion Sources/D2Commands/Game/Wordle/WordleBoard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public struct WordleBoard: RichValueConvertible, Sendable {
}
}

public enum Clue: UInt32, CaseIterable {
public enum Clue: UInt32, CaseIterable, Sendable, Hashable {
case unknown = 0
case nowhere
case somewhere
Expand Down
5 changes: 5 additions & 0 deletions Sources/D2TestUtils/Double+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public extension Double {
func isApproximatelyEqual(to other: Self, accuracy: Double = 0.0001) -> Bool {
abs(self - other) < accuracy
}
}
26 changes: 13 additions & 13 deletions Tests/D2CommandTests/Game/Chess/ChessStateTests.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import XCTest
import Testing
@testable import D2Commands

final class ChessStateTests: XCTestCase {
func testPossibleMoves() throws {
struct ChessStateTests {
@Test func possibleMoves() throws {
let state = ChessState(players: [GamePlayer(username: "Mr. White"), GamePlayer(username: "Mr. Black")])
let initialMoves = state.possibleMoves

XCTAssert(initialMoves.allSatisfy { $0.color == .white }, "Initial moves should all be white")
XCTAssert(initialMoves.allSatisfy { $0.isCapture == false }, "Initial moves should not contain captures (or unspecified 'isCapture' fields)")
XCTAssert(initialMoves.contains(ChessMove(
#expect(initialMoves.allSatisfy { $0.color == .white }, "Initial moves should all be white")
#expect(initialMoves.allSatisfy { $0.isCapture == false }, "Initial moves should not contain captures (or unspecified 'isCapture' fields)")
#expect(initialMoves.contains(ChessMove(
pieceType: .pawn,
color: .white,
originX: xOf(file: "e"),
Expand All @@ -18,19 +18,19 @@ final class ChessStateTests: XCTestCase {
destinationY: yOf(rank: 4),
isEnPassant: false
)), "Possible moves should contain pawn move e4e6")
assert(initialMoves, containsMove: "white Nb1c3")
expect(initialMoves, containsMove: "white Nb1c3")

let secondState = try state.childState(after: move("white e2e4", in: state.possibleMoves)!)
let secondMoves = secondState.possibleMoves

XCTAssert(secondMoves.allSatisfy { $0.color == .black }, "Second moves should all be black")
assert(secondMoves, containsMove: "black e7e6")
assert(secondMoves, containsMove: "black b7b5")
assert(secondMoves, containsMove: "black Ng8f6")
#expect(secondMoves.allSatisfy { $0.color == .black }, "Second moves should all be black")
expect(secondMoves, containsMove: "black e7e6")
expect(secondMoves, containsMove: "black b7b5")
expect(secondMoves, containsMove: "black Ng8f6")
}

private func assert(_ moves: Set<ChessMove>, containsMove moveDescription: String) {
XCTAssert(moves.contains { $0.description == moveDescription }, "Moves should contain '\(moveDescription)', but did not: \(moves)")
private func expect(_ moves: Set<ChessMove>, containsMove moveDescription: String, sourceLocation: SourceLocation = #_sourceLocation) {
#expect(moves.contains { $0.description == moveDescription }, "Moves should contain '\(moveDescription)', but did not: \(moves)", sourceLocation: sourceLocation)
}

private func move(_ description: String, in moves: Set<ChessMove>) -> ChessMove? {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
import XCTest
import Testing
@testable import D2Commands

final class ChessNotationParserTests: XCTestCase {
func testShortAlgebraicNotation() throws {
struct ChessNotationParserTests {
@Test func shortAlgebraicNotation() {
let parser = ShortAlgebraicNotationParser()

let bishopMove = parser.parse("Lc4")
XCTAssertEqual(bishopMove?.pieceType, ChessPieceType.bishop)
XCTAssertEqual(bishopMove?.destinationX, 2)
XCTAssertEqual(bishopMove?.destinationY, 4)
XCTAssertEqual(bishopMove?.isCapture, false)
#expect(bishopMove?.pieceType == ChessPieceType.bishop)
#expect(bishopMove?.destinationX == 2)
#expect(bishopMove?.destinationY == 4)
#expect(bishopMove?.isCapture == false)

let captureMove = parser.parse("Bxb5")
XCTAssertEqual(captureMove?.pieceType, ChessPieceType.bishop)
XCTAssertEqual(captureMove?.isCapture, true)
XCTAssertEqual(captureMove?.destinationX, 1)
XCTAssertEqual(captureMove?.destinationY, 3)
#expect(captureMove?.pieceType == ChessPieceType.bishop)
#expect(captureMove?.isCapture == true)
#expect(captureMove?.destinationX == 1)
#expect(captureMove?.destinationY == 3)

let pawnMove = parser.parse("g3")
XCTAssertEqual(pawnMove?.pieceType, ChessPieceType.pawn)
XCTAssertEqual(pawnMove?.isCapture, false)
XCTAssertEqual(pawnMove?.isEnPassant, false)
XCTAssertEqual(pawnMove?.destinationX, 6)
XCTAssertEqual(pawnMove?.destinationY, 5)
#expect(pawnMove?.pieceType == ChessPieceType.pawn)
#expect(pawnMove?.isCapture == false)
#expect(pawnMove?.isEnPassant == false)
#expect(pawnMove?.destinationX == 6)
#expect(pawnMove?.destinationY == 5)

let enPassantMove = parser.parse("fxg6 e. p.")
XCTAssertEqual(enPassantMove?.pieceType, ChessPieceType.pawn)
XCTAssertEqual(enPassantMove?.isCapture, true)
XCTAssertEqual(enPassantMove?.isEnPassant, true)
XCTAssertEqual(enPassantMove?.originX, 5)
XCTAssertEqual(enPassantMove?.destinationX, 6)
XCTAssertEqual(enPassantMove?.destinationY, 2)
#expect(enPassantMove?.pieceType == ChessPieceType.pawn)
#expect(enPassantMove?.isCapture == true)
#expect(enPassantMove?.isEnPassant == true)
#expect(enPassantMove?.originX == 5)
#expect(enPassantMove?.destinationX == 6)
#expect(enPassantMove?.destinationY == 2)

let knightMove1 = parser.parse("Sac7")
XCTAssertEqual(knightMove1?.pieceType, ChessPieceType.knight)
XCTAssertEqual(knightMove1?.originX, 0)
XCTAssertEqual(knightMove1?.destinationX, 2)
XCTAssertEqual(knightMove1?.destinationY, 1)
#expect(knightMove1?.pieceType == ChessPieceType.knight)
#expect(knightMove1?.originX == 0)
#expect(knightMove1?.destinationX == 2)
#expect(knightMove1?.destinationY == 1)

let knightMove2 = parser.parse("Ne1xc4")
XCTAssertEqual(knightMove2?.pieceType, ChessPieceType.knight)
XCTAssertEqual(knightMove2?.originX, 4)
XCTAssertEqual(knightMove2?.originY, 7)
XCTAssertEqual(knightMove2?.isCapture, true)
XCTAssertEqual(knightMove2?.destinationX, 2)
XCTAssertEqual(knightMove2?.destinationY, 4)
#expect(knightMove2?.pieceType == ChessPieceType.knight)
#expect(knightMove2?.originX == 4)
#expect(knightMove2?.originY == 7)
#expect(knightMove2?.isCapture == true)
#expect(knightMove2?.destinationX == 2)
#expect(knightMove2?.destinationY == 4)

let rookMove1 = parser.parse("R1c7")
XCTAssertEqual(rookMove1?.pieceType, ChessPieceType.rook)
XCTAssertEqual(rookMove1?.originY, 7)
XCTAssertEqual(rookMove1?.destinationX, 2)
XCTAssertEqual(rookMove1?.destinationY, 1)
#expect(rookMove1?.pieceType == ChessPieceType.rook)
#expect(rookMove1?.originY == 7)
#expect(rookMove1?.destinationX == 2)
#expect(rookMove1?.destinationY == 1)

let rookMove2 = parser.parse("Th2")
XCTAssertEqual(rookMove2?.pieceType, ChessPieceType.rook)
XCTAssertEqual(rookMove2?.destinationX, 7)
XCTAssertEqual(rookMove2?.destinationY, 6)
#expect(rookMove2?.pieceType == ChessPieceType.rook)
#expect(rookMove2?.destinationX == 7)
#expect(rookMove2?.destinationY == 6)
}
}
30 changes: 15 additions & 15 deletions Tests/D2CommandTests/Game/Chess/Pieces/ChessPieceUtilsTests.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import XCTest
import Testing
import Utils
@testable import D2Commands

final class ChessPieceUtilsTests: XCTestCase {
func testNeighborFields() throws {
XCTAssertEqual(Set(neighborFields()), Set([
struct ChessPieceUtilsTests {
@Test func neighborFieldsAsExpected() {
#expect(Set(neighborFields()) == Set([
Vec2(x: -1, y: -1),
Vec2(x: 0, y: -1),
Vec2(x: 1, y: -1),
Expand All @@ -16,18 +16,18 @@ final class ChessPieceUtilsTests: XCTestCase {
]))
}

func testPieceLetters() throws {
assert("Q", matchesPiece: .queen)
assert("D", matchesPiece: .queen)
assert("R", matchesPiece: .rook)
assert("T", matchesPiece: .rook)
assert("L", matchesPiece: .bishop)
assert("B", matchesPiece: .bishop)
assert("N", matchesPiece: .knight)
assert("S", matchesPiece: .knight)
@Test func pieceLetters() {
expect("Q", matchesPiece: .queen)
expect("D", matchesPiece: .queen)
expect("R", matchesPiece: .rook)
expect("T", matchesPiece: .rook)
expect("L", matchesPiece: .bishop)
expect("B", matchesPiece: .bishop)
expect("N", matchesPiece: .knight)
expect("S", matchesPiece: .knight)
}

private func assert(_ letter: Character, matchesPiece pieceType: ChessPieceType) {
XCTAssertEqual(pieceOf(letter: letter)?.pieceType, pieceType)
private func expect(_ letter: Character, matchesPiece pieceType: ChessPieceType, sourceLocation: SourceLocation = #_sourceLocation) {
#expect(pieceOf(letter: letter)?.pieceType == pieceType, sourceLocation: sourceLocation)
}
}
40 changes: 15 additions & 25 deletions Tests/D2CommandTests/Game/TicTacToe/TicTacToeCommandTests.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import XCTest
import Testing
import D2MessageIO
import D2TestUtils
@testable import D2Commands
Expand All @@ -9,53 +9,43 @@ fileprivate let e = ":white_large_square:"
fileprivate let nameX = "Mr. X"
fileprivate let nameO = "Mr. O"

final class TicTacToeCommandTests: XCTestCase {
struct TicTacToeCommandTests {
private let playerX = GamePlayer(username: nameX)
private let playerO = GamePlayer(username: nameO)

func testXWin() async throws {
@Test func xWin() async {
let command = await GameCommand<TicTacToeGame>()
let output = await TestOutput()
var lastContent: String?
var lastEmbedDescription: String?

let channel = dummyId
await command.startMatch(between: [playerX, playerO], on: channel, output: output)

await command.perform("move", withArgs: "top left", on: channel, output: output, author: playerO)
lastEmbedDescription = await output.lastEmbedDescription
XCTAssertEqual(lastEmbedDescription, ":warning: It is not your turn, `\(nameO)`")
#expect(await output.lastEmbedDescription == ":warning: It is not your turn, `\(nameO)`")

await command.perform("move", withArgs: "top left", on: channel, output: output, author: playerX)
lastContent = await output.lastContent
XCTAssertEqual(lastContent, "\(x)\(e)\(e)\n\(e)\(e)\(e)\n\(e)\(e)\(e)")
#expect(await output.lastContent == "\(x)\(e)\(e)\n\(e)\(e)\(e)\n\(e)\(e)\(e)")

await command.perform("move", withArgs: "top right", on: channel, output: output, author: playerX)
lastEmbedDescription = await output.lastEmbedDescription
XCTAssertEqual(lastEmbedDescription, ":warning: It is not your turn, `\(nameX)`")
#expect(await output.lastEmbedDescription == ":warning: It is not your turn, `\(nameX)`")

await command.perform("move", withArgs: "center center", on: channel, output: output, author: playerO)
lastContent = await output.lastContent
XCTAssertEqual(lastContent, "\(x)\(e)\(e)\n\(e)\(o)\(e)\n\(e)\(e)\(e)")
#expect(await output.lastContent == "\(x)\(e)\(e)\n\(e)\(o)\(e)\n\(e)\(e)\(e)")

await command.perform("move", withArgs: "left bottom", on: channel, output: output, author: playerX)
lastContent = await output.lastContent
XCTAssertEqual(lastContent, "\(x)\(e)\(e)\n\(e)\(o)\(e)\n\(x)\(e)\(e)")
#expect(await output.lastContent == "\(x)\(e)\(e)\n\(e)\(o)\(e)\n\(x)\(e)\(e)")

await command.perform("move", withArgs: "0 2", on: channel, output: output, author: playerO)
lastContent = await output.lastContent
XCTAssertEqual(lastContent, "\(x)\(e)\(o)\n\(e)\(o)\(e)\n\(x)\(e)\(e)")
#expect(await output.lastContent == "\(x)\(e)\(o)\n\(e)\(o)\(e)\n\(x)\(e)\(e)")

await command.perform("move", withArgs: "1 0", on: channel, output: output, author: playerX)
lastContent = await output.lastContent
XCTAssertEqual(lastContent, "\(x)\(e)\(o)\n\(x)\(o)\(e)\n\(x)\(e)\(e)")
#expect(await output.lastContent == "\(x)\(e)\(o)\n\(x)\(o)\(e)\n\(x)\(e)\(e)")

let result = await output.last?.embeds.first
XCTAssertEqual(result?.title, ":crown: Winner")
XCTAssertEqual(result?.description, "\(x) aka. `\(nameX)` won the game!")
#expect(result?.title == ":crown: Winner")
#expect(result?.description == "\(x) aka. `\(nameX)` won the game!")
}

func testDraw() async throws {
@Test func draw() async {
let command = await GameCommand<TicTacToeGame>()
let output = await TestOutput()
let channel = dummyId
Expand All @@ -72,7 +62,7 @@ final class TicTacToeCommandTests: XCTestCase {
await command.perform("move", withArgs: "0 2", on: channel, output: output, author: playerX)

let result = await output.last?.embeds.first
XCTAssertEqual(result?.title, ":crown: Game Over")
XCTAssertEqual(result?.description, "The game resulted in a draw!")
#expect(result?.title == ":crown: Game Over")
#expect(result?.description == "The game resulted in a draw!")
}
}
22 changes: 11 additions & 11 deletions Tests/D2CommandTests/Game/Wordle/WordleBoardTests.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import XCTest
import Testing
@testable import D2Commands

final class WordleBoardTests: XCTestCase {
func testClues() throws {
XCTAssertEqual(WordleBoard.Clues(fromArray: [.here]).count, 1)
XCTAssertEqual(WordleBoard.Clues(fromArray: [.nowhere, .somewhere]).count, 2)

testCodingRoundtrip(for: [])
testCodingRoundtrip(for: [.somewhere, .unknown])
testCodingRoundtrip(for: [.nowhere, .somewhere, .somewhere, .here, .nowhere])
struct WordleBoardTests {
@Test func clues() {
#expect(WordleBoard.Clues(fromArray: [.here]).count == 1)
#expect(WordleBoard.Clues(fromArray: [.nowhere, .somewhere]).count == 2)
}

private func testCodingRoundtrip(for clues: [WordleBoard.Clue]) {
XCTAssertEqual(Array(WordleBoard.Clues(fromArray: clues)), clues)
@Test(arguments: [
[WordleBoard.Clue](),
[.somewhere, .unknown],
[.nowhere, .somewhere, .somewhere, .here, .nowhere],
]) func codingRoundtrip(clues: [WordleBoard.Clue]) {
#expect(Array(WordleBoard.Clues(fromArray: clues)) == clues)
}
}
47 changes: 23 additions & 24 deletions Tests/D2CommandTests/Math/Parse/ExpressionParserTests.swift
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
import XCTest
import Testing
import D2TestUtils
@testable import D2Commands

fileprivate let eps = 0.00001

final class ExpressionParserTests: XCTestCase {
func testRPNExpressionParser() throws {
struct ExpressionParserTests {
@Test func rpnExpressionParser() throws {
let parser = RPNExpressionParser()

let rawProduct = "3 4 *"
guard let product = try parser.parse(rawProduct) as? ProductNode else { XCTFail("\(rawProduct) should be a product node"); return }
XCTAssertEqual(try product.lhs.evaluate(), 3.0, accuracy: eps)
XCTAssertEqual(try product.rhs.evaluate(), 4.0, accuracy: eps)
guard let product = try parser.parse(rawProduct) as? ProductNode else { Issue.record("\(rawProduct) should be a product node"); return }
#expect(try product.lhs.evaluate().isApproximatelyEqual(to: 3.0))
#expect(try product.rhs.evaluate().isApproximatelyEqual(to: 4.0))

let rawQuotient = "2.1 -51.09 pi + 1 - /"
guard let quotient = try parser.parse(rawQuotient) as? QuotientNode else { XCTFail("\(rawQuotient) should be a quotient node"); return }
guard let quotientLeft = quotient.lhs as? ConstantNode else { XCTFail("Left-hand side of quotient should be a constant"); return }
guard let quotientRight = quotient.rhs as? DifferenceNode else { XCTFail("Right-hand side of quotient should be a difference"); return }
guard let differenceLeft = quotientRight.lhs as? SumNode else { XCTFail("Left-hand side of difference should be a sum"); return }
guard let differenceRight = quotientRight.rhs as? ConstantNode else { XCTFail("Right-hand side of quotient should be a constant"); return }
guard let sumLeft = differenceLeft.lhs as? ConstantNode else { XCTFail("Left-hand side of sum should be a constant"); return }
guard let sumRight = differenceLeft.rhs as? ConstantNode else { XCTFail("Right-hand side of sum should be a constant"); return }
XCTAssertEqual(try quotientLeft.evaluate(), 2.1, accuracy: eps)
XCTAssertEqual(try sumLeft.evaluate(), -51.09, accuracy: eps)
XCTAssertEqual(try sumRight.evaluate(), Double.pi, accuracy: eps)
XCTAssertEqual(try differenceRight.evaluate(), 1.0, accuracy: eps)
guard let quotient = try parser.parse(rawQuotient) as? QuotientNode else { Issue.record("\(rawQuotient) should be a quotient node"); return }
guard let quotientLeft = quotient.lhs as? ConstantNode else { Issue.record("Left-hand side of quotient should be a constant"); return }
guard let quotientRight = quotient.rhs as? DifferenceNode else { Issue.record("Right-hand side of quotient should be a difference"); return }
guard let differenceLeft = quotientRight.lhs as? SumNode else { Issue.record("Left-hand side of difference should be a sum"); return }
guard let differenceRight = quotientRight.rhs as? ConstantNode else { Issue.record("Right-hand side of quotient should be a constant"); return }
guard let sumLeft = differenceLeft.lhs as? ConstantNode else { Issue.record("Left-hand side of sum should be a constant"); return }
guard let sumRight = differenceLeft.rhs as? ConstantNode else { Issue.record("Right-hand side of sum should be a constant"); return }
#expect(try quotientLeft.evaluate().isApproximatelyEqual(to: 2.1))
#expect(try sumLeft.evaluate().isApproximatelyEqual(to: -51.09))
#expect(try sumRight.evaluate().isApproximatelyEqual(to: Double.pi))
#expect(try differenceRight.evaluate().isApproximatelyEqual(to: 1.0))
}

func testInfixExpressionParser() throws {
@Test func infixExpressionParser() throws {
let parser = InfixExpressionParser()

XCTAssertTrue(try parser.parse("3 * 4").isEqual(to: ProductNode(lhs: ConstantNode(value: 3), rhs: ConstantNode(value: 4))))
XCTAssertTrue(try parser.parse("3 + 4").isEqual(to: SumNode(lhs: ConstantNode(value: 3), rhs: ConstantNode(value: 4))))
XCTAssertTrue(try parser.parse("4 - 9 * 8").isEqual(to: DifferenceNode(lhs: ConstantNode(value: 4), rhs: ProductNode(lhs: ConstantNode(value: 9), rhs: ConstantNode(value: 8)))))
XCTAssertTrue(try parser.parse("(4 - 9) * 8").isEqual(to: ProductNode(lhs: DifferenceNode(lhs: ConstantNode(value: 4), rhs: ConstantNode(value: 9)), rhs: ConstantNode(value: 8))))
#expect(try parser.parse("3 * 4").isEqual(to: ProductNode(lhs: ConstantNode(value: 3), rhs: ConstantNode(value: 4))))
#expect(try parser.parse("3 + 4").isEqual(to: SumNode(lhs: ConstantNode(value: 3), rhs: ConstantNode(value: 4))))
#expect(try parser.parse("4 - 9 * 8").isEqual(to: DifferenceNode(lhs: ConstantNode(value: 4), rhs: ProductNode(lhs: ConstantNode(value: 9), rhs: ConstantNode(value: 8)))))
#expect(try parser.parse("(4 - 9) * 8").isEqual(to: ProductNode(lhs: DifferenceNode(lhs: ConstantNode(value: 4), rhs: ConstantNode(value: 9)), rhs: ConstantNode(value: 8))))
}
}
Loading

0 comments on commit b5a46a2

Please sign in to comment.