Skip to content

Commit b201de7

Browse files
committed
Refactor dealer command and tests
Accept multiple arguments Throw errors to indicate failure
1 parent 5ca178d commit b201de7

File tree

4 files changed

+99
-52
lines changed

4 files changed

+99
-52
lines changed

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import PackageDescription
1515

1616
let package = Package(
1717
name: "dealer",
18+
platforms: [
19+
.macOS(.v11)
20+
],
1821
products: [
1922
.executable(name: "dealer", targets: ["dealer"]),
2023
],

Sources/dealer/Deal.swift

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,33 @@ import Glibc
1414

1515
import Foundation
1616
import DeckOfPlayingCards
17+
import PlayingCard
1718
import ArgumentParser
1819

19-
var stdout = FileHandle.standardOutput
20-
var stderr = FileHandle.standardError
21-
2220
@main
2321
struct Deal: ParsableCommand {
22+
enum Error: Swift.Error, CustomStringConvertible {
23+
case notEnoughCards
24+
25+
var description: String {
26+
switch self {
27+
case .notEnoughCards:
28+
return "Not enough cards"
29+
}
30+
}
31+
}
32+
2433
static var configuration = CommandConfiguration(
2534
abstract: "Shuffles a deck of playing cards and deals a number of cards.",
2635
discussion: """
27-
Prints each card to stdout until the deck is completely dealt,
28-
and prints "No more cards" to stderr if there are no cards remaining.
36+
For each count argument, prints a line of tab-delimited cards to stdout,
37+
or if there aren't enough cards remaining,
38+
prints "Not enough cards" to stderr and exits with a nonzero status.
2939
""")
3040

31-
@Argument(help: "The number of cards to deal.")
32-
var count: UInt = 10
41+
@Argument(help: .init("The number of cards to deal at a time.",
42+
valueName: "count"))
43+
var counts: [UInt]
3344

3445
mutating func run() throws {
3546
#if os(Linux)
@@ -39,13 +50,18 @@ struct Deal: ParsableCommand {
3950
var deck = Deck.standard52CardDeck()
4051
deck.shuffle()
4152

42-
for _ in 0..<count {
43-
guard let card = deck.deal() else {
44-
print("No more cards", to: &stderr)
45-
break
53+
for count in counts {
54+
var cards: [PlayingCard] = []
55+
56+
for _ in 0..<count {
57+
guard let card = deck.deal() else {
58+
Self.exit(withError: Error.notEnoughCards)
59+
}
60+
61+
cards.append(card)
4662
}
4763

48-
print(card, to: &stdout)
64+
print(cards.map(\.description).joined(separator: "\t"))
4965
}
5066
}
5167
}

Sources/dealer/FileWrapper+Extensions.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

Tests/DealerTests/DealerTests.swift

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,43 @@ import XCTest
1212
import class Foundation.Bundle
1313

1414
final class DealerTests: XCTestCase {
15-
func testExample() throws {
16-
// This is an example of a functional test case.
17-
// Use XCTAssert and related functions to verify your tests produce the correct
18-
// results.
19-
20-
// Some of the APIs that we use below are available in macOS 10.13 and above.
21-
guard #available(macOS 10.13, *) else {
22-
return
23-
}
15+
func testUsage() throws {
16+
let (status, output, error) = try execute(with: ["--help"])
17+
XCTAssertEqual(status, EXIT_SUCCESS)
18+
XCTAssert(output?.starts(with: "OVERVIEW: Shuffles a deck of playing cards and deals a number of cards.") ?? false)
19+
XCTAssertEqual(error, "")
20+
}
2421

25-
// Mac Catalyst won't have `Process`, but it is supported for executables.
26-
#if !targetEnvironment(macCatalyst)
22+
func testDealOneCard() throws {
23+
let (status, output, error) = try execute(with: ["1"])
24+
XCTAssertEqual(status, EXIT_SUCCESS)
25+
XCTAssertEqual(output?.filter(\.isPlayingCardSuit).count, 1)
2726

28-
let dealerBinary = productsDirectory.appendingPathComponent("dealer")
27+
XCTAssertEqual(error, "")
28+
}
2929

30-
let process = Process()
31-
process.executableURL = dealerBinary
30+
func testDealTenCards() throws {
31+
let (status, output, error) = try execute(with: ["10"])
32+
XCTAssertEqual(status, EXIT_SUCCESS)
33+
XCTAssertEqual(output?.filter(\.isPlayingCardSuit).count, 10)
3234

33-
let pipe = Pipe()
34-
process.standardOutput = pipe
35+
XCTAssertEqual(error, "")
36+
}
3537

36-
try process.run()
37-
process.waitUntilExit()
38+
func testDealThirteenCardsFourTimes() throws {
39+
let (status, output, error) = try execute(with: ["13", "13", "13", "13"])
40+
XCTAssertEqual(status, EXIT_SUCCESS)
41+
XCTAssertEqual(output?.filter(\.isPlayingCardSuit).count, 52)
42+
XCTAssertEqual(output?.filter(\.isNewline).count, 4)
3843

39-
let data = pipe.fileHandleForReading.readDataToEndOfFile()
40-
let output = String(data: data, encoding: .utf8)
44+
XCTAssertEqual(error, "")
45+
}
4146

42-
XCTAssertNotEqual(output, "")
43-
#endif
47+
func testDealOneHundredCards() throws {
48+
let (status, output, error) = try execute(with: ["100"])
49+
XCTAssertNotEqual(status, EXIT_SUCCESS)
50+
XCTAssertEqual(output, "")
51+
XCTAssertEqual(error, "Error: Not enough cards\n")
4452
}
4553

4654
/// Returns path to the built products directory.
@@ -54,4 +62,42 @@ final class DealerTests: XCTestCase {
5462
return Bundle.main.bundleURL
5563
#endif
5664
}
65+
66+
private func execute(with arguments: [String] = []) throws -> (status: Int32, output: String?, error: String?) {
67+
let process = Process()
68+
process.executableURL = productsDirectory.appendingPathComponent("dealer")
69+
process.arguments = arguments
70+
71+
let outputPipe = Pipe()
72+
process.standardOutput = outputPipe
73+
74+
let errorPipe = Pipe()
75+
process.standardError = errorPipe
76+
77+
try process.run()
78+
process.waitUntilExit()
79+
80+
let status = process.terminationStatus
81+
82+
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
83+
let output = String(data: outputData, encoding: .utf8)
84+
85+
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
86+
let error = String(data: errorData, encoding: .utf8)
87+
88+
return (status, output, error)
89+
}
90+
}
91+
92+
// MARK: -
93+
94+
private extension Character {
95+
var isPlayingCardSuit: Bool {
96+
switch self {
97+
case "♠︎", "", "", "♣︎":
98+
return true
99+
default:
100+
return false
101+
}
102+
}
57103
}

0 commit comments

Comments
 (0)