diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme
index 5b968ef..3ecbb41 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/commitPrefix.xcscheme
@@ -34,6 +34,48 @@
ReferencedContainer = "container:">
+
+
+
+
+
+
+
+
+
+
+
+
private let userEntry: PositionalArgument<[String]>
- init(arguments: [String] = CommandLine.arguments) {
+ public init(arguments: [String] = CommandLine.arguments) {
// The first argument specifies the path of the executable file
self.rawArgs = Array(arguments.dropFirst())
let argBuilder = ArgumentBuilder()
@@ -73,7 +73,7 @@ struct CLIArguments {
self.userEntry = argBuilder.buildUserEntryArgument(parser: parser)
}
- private func singleCommandParse(_ allCommands: [ParsedCommand]) -> Result {
+ private func singleCommandParse(_ allCommands: [ParsedCommand]) -> Result {
precondition(allCommands.count == 1, "Intended for single Parsed Command only!")
guard let foundCommand = allCommands.first else {
return .failure(.commandNotRecognized)
@@ -95,7 +95,7 @@ struct CLIArguments {
}
}
- private func doubleCommandParse(_ allCommands: [ParsedCommand]) -> Result {
+ private func doubleCommandParse(_ allCommands: [ParsedCommand]) -> Result {
precondition(allCommands.count == 2, "Intended for two Parsed Commands only!")
let firstCommand = allCommands[0]
let secondCommand = allCommands[1]
@@ -110,7 +110,7 @@ struct CLIArguments {
}
}
- private func getCommand() -> Result {
+ public func getCommand() -> Result {
guard let parsedArgs = try? parser.parse(rawArgs) else {
return .failure(.commandNotRecognized)
}
@@ -126,12 +126,12 @@ struct CLIArguments {
do {
try parsedArgs.get(userEntry).map { userEntry in
let noMoreThanOneEntry = userEntry.count < 2
- guard noMoreThanOneEntry else { throw CPError.invalidEntryFormat }
- guard let theEntry = userEntry.first else { throw CPError.emptyEntry }
+ guard noMoreThanOneEntry else { throw CLIError.invalidEntryFormat }
+ guard let theEntry = userEntry.first else { throw CLIError.emptyEntry }
allCommands.append(.userEntry(value: theEntry))
}
- } catch let cpError as CPError {
- return .failure(cpError)
+ } catch let cliError as CLIError {
+ return .failure(cliError)
} catch {
return .failure(.unexpectedError)
}
@@ -149,6 +149,6 @@ struct CLIArguments {
}
- var command: UserCommand { getCommand().resolveOrExit() }
+ public var command: UserCommand { getCommand().resolveOrExit() }
}
diff --git a/Sources/CLInterface/CLIError.swift b/Sources/CLInterface/CLIError.swift
new file mode 100644
index 0000000..37ab2a9
--- /dev/null
+++ b/Sources/CLInterface/CLIError.swift
@@ -0,0 +1,114 @@
+//
+// CLIError.swift
+// commitPrefix
+//
+// MIT License
+//
+// Copyright (c) 2020 STEPHEN L. MARTINEZ
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import Foundation
+import Consler
+
+public enum TerminationStatus: Int32 {
+ /// Used when the app finishes as expected
+ case successful
+
+ /// Used when an error that has not been accounted for has been thrown
+ case unexpectedError
+
+ /// Used when the user takes an action that stops the application short
+ case userInitiated
+
+ /// Used when the inputs provided to the app are invalid
+ case invalidInputs
+
+ /// Used when the app can no longer continue due to user specified settings
+ case invalidContext
+
+ /// Used when required resources are inaccessible or unavailable
+ case unavailableDependencies
+
+ public var value: Int32 { self.rawValue }
+}
+
+public enum CLIError: Error, Equatable {
+
+ case commandNotRecognized
+ case tooManyArguments
+ case emptyEntry
+ case invalidEntryFormat
+ case unexpectedError
+
+ public var message: ConslerOutput {
+ switch self {
+ case .commandNotRecognized:
+ return ConslerOutput(
+ "Error: ", "Command not recognized. Enter ", "\"--help\"", " for usage.")
+ .describedBy(.boldRed, .normal, .cyan)
+
+ case .tooManyArguments:
+ return ConslerOutput(
+ "Error: ", "Too many arguments entered. Only two at a time is supported.")
+ .describedBy(.boldRed)
+
+ case .emptyEntry:
+ return ConslerOutput("Error: ", "Your entry is empty.").describedBy(.boldRed)
+
+ case .invalidEntryFormat:
+ return ConslerOutput("Error: ", "Your entry contains invalid spaces.")
+ .describedBy(.boldRed)
+
+ case .unexpectedError:
+ return ConslerOutput("Error: ", "An uncategorized error has occured")
+ .describedBy(.boldRed)
+ }
+ }
+
+ public var status: TerminationStatus {
+ switch self {
+ case .commandNotRecognized:
+ return .invalidInputs
+ case .tooManyArguments:
+ return .invalidInputs
+ case .emptyEntry:
+ return .invalidInputs
+ case .invalidEntryFormat:
+ return .invalidInputs
+ case .unexpectedError:
+ return .unexpectedError
+ }
+ }
+
+}
+
+extension Result where Failure == CLIError {
+
+ func resolveOrExit() -> Success {
+ switch self {
+ case let .success(value):
+ return value
+ case let .failure(cliError):
+ Consler.output(cliError.message, type: .error)
+ exit(cliError.status.value)
+ }
+ }
+
+}
diff --git a/Sources/CommitPrefix/CPInteractor.swift b/Sources/CommitPrefix/CPInteractor.swift
index 0bd516e..58bf320 100644
--- a/Sources/CommitPrefix/CPInteractor.swift
+++ b/Sources/CommitPrefix/CPInteractor.swift
@@ -27,6 +27,7 @@
import Consler
import Files
import Foundation
+import FoundationExt
struct CPInteractor {
diff --git a/Sources/CommitPrefix/Constants.swift b/Sources/CommitPrefix/Constants.swift
index 6f0a4ad..6330182 100644
--- a/Sources/CommitPrefix/Constants.swift
+++ b/Sources/CommitPrefix/Constants.swift
@@ -28,7 +28,7 @@ import Foundation
struct CPInfo {
- static let version = "1.4.3"
+ static let version = "1.4.4"
}
diff --git a/Sources/CommitPrefix/Error+Debug/CPError.swift b/Sources/CommitPrefix/Error+Debug/CPError.swift
index 714c235..1795342 100644
--- a/Sources/CommitPrefix/Error+Debug/CPError.swift
+++ b/Sources/CommitPrefix/Error+Debug/CPError.swift
@@ -24,43 +24,16 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+import enum CLInterface.TerminationStatus
import Consler
import Foundation
-enum TerminationStatus: Int32 {
- /// Used when the app finishes as expected
- case successful
-
- /// Used when an error that has not been accounted for has been thrown
- case unexpectedError
-
- /// Used when the user takes an action that stops the application short
- case userInitiated
-
- /// Used when the inputs provided to the app are invalid
- case invalidInputs
-
- /// Used when the app can no longer continue due to user specified settings
- case invalidContext
-
- /// Used when required resources are inaccessible or unavailable
- case unavailableDependencies
-
- var value: Int32 { self.rawValue }
-}
-
enum CPError: Error {
- // MARK: - CLI Errors
- case commandNotRecognized
- case tooManyArguments
- case emptyEntry
-
// MARK: - User Termination Errors
case overwriteCancelled
// MARK: - Format Errors
- case invalidEntryFormat
case invalidBranchValidatorFormat
case invalidBranchPrefix(validator: String)
case invalidYesOrNoFormat
@@ -81,26 +54,9 @@ enum CPError: Error {
var message: ConslerOutput {
switch self {
- case .commandNotRecognized:
- return ConslerOutput(
- "Error: ", "Command not recognized. Enter ", "\"--help\"", " for usage.")
- .describedBy(.boldRed, .normal, .cyan)
-
- case .tooManyArguments:
- return ConslerOutput(
- "Error: ", "Too many arguments entered. Only two at a time is supported.")
- .describedBy(.boldRed)
-
- case .emptyEntry:
- return ConslerOutput("Error: ", "Your entry is empty.").describedBy(.boldRed)
-
case .overwriteCancelled:
return ConslerOutput("Error: ", "Overwrite is cancelled").describedBy(.boldRed)
- case .invalidEntryFormat:
- return ConslerOutput("Error: ", "Your entry contains invalid spaces.")
- .describedBy(.boldRed)
-
case .invalidBranchValidatorFormat:
return ConslerOutput(
"Error: ", "The branch validator must be at least two characters long ",
@@ -155,16 +111,8 @@ enum CPError: Error {
var status: TerminationStatus {
switch self {
- case .commandNotRecognized:
- return .invalidInputs
- case .tooManyArguments:
- return .invalidInputs
- case .emptyEntry:
- return .invalidInputs
case .overwriteCancelled:
return .userInitiated
- case .invalidEntryFormat:
- return .invalidInputs
case .invalidBranchValidatorFormat:
return .invalidInputs
case .invalidBranchPrefix:
@@ -190,3 +138,17 @@ enum CPError: Error {
}
}
+
+extension Result where Failure == CPError {
+
+ func resolveOrExit() -> Success {
+ switch self {
+ case let .success(value):
+ return value
+ case let .failure(cpError):
+ Consler.output(cpError.message, type: .error)
+ exit(cpError.status.value)
+ }
+ }
+
+}
diff --git a/Sources/CommitPrefix/Hook/CommitMessageHook.swift b/Sources/CommitPrefix/Hook/CommitMessageHook.swift
index 934c7c2..ecdd080 100644
--- a/Sources/CommitPrefix/Hook/CommitMessageHook.swift
+++ b/Sources/CommitPrefix/Hook/CommitMessageHook.swift
@@ -27,6 +27,7 @@
import Consler
import Files
import Foundation
+import FoundationExt
struct CommitMessageHook {
diff --git a/Sources/CommitPrefix/main.swift b/Sources/CommitPrefix/main.swift
index 22bce7b..3f0bc41 100644
--- a/Sources/CommitPrefix/main.swift
+++ b/Sources/CommitPrefix/main.swift
@@ -24,6 +24,7 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+import CLInterface
import Consler
import Foundation
diff --git a/Sources/CommitPrefix/Utilities/Result+Extensions.swift b/Sources/FoundationExt/Result+Ext.swift
similarity index 78%
rename from Sources/CommitPrefix/Utilities/Result+Extensions.swift
rename to Sources/FoundationExt/Result+Ext.swift
index 2bfafc4..6f00522 100644
--- a/Sources/CommitPrefix/Utilities/Result+Extensions.swift
+++ b/Sources/FoundationExt/Result+Ext.swift
@@ -25,9 +25,8 @@
// SOFTWARE.
import Foundation
-import Consler
-extension Result {
+public extension Result {
func transform(_ newValue: NewSuccess) -> Result {
switch self {
@@ -38,17 +37,24 @@ extension Result {
}
}
-}
-
-extension Result where Failure == CPError {
+ var optional: Success? {
+ switch self {
+ case let .success(successValue):
+ return successValue
+ case .failure:
+ return nil
+ }
+ }
- func resolveOrExit() -> Success {
+ func `do`(
+ onFailure: @escaping (Failure) -> Void = { _ in },
+ onSuccess: @escaping (Success) -> Void
+ ) {
switch self {
case let .success(value):
- return value
- case let .failure(cpError):
- Consler.output(cpError.message, type: .error)
- exit(cpError.status.value)
+ onSuccess(value)
+ case let .failure(error):
+ onFailure(error)
}
}
diff --git a/Sources/CommitPrefix/Utilities/Shell.swift b/Sources/FoundationExt/Shell.swift
similarity index 95%
rename from Sources/CommitPrefix/Utilities/Shell.swift
rename to Sources/FoundationExt/Shell.swift
index 8cace1d..b54d05a 100644
--- a/Sources/CommitPrefix/Utilities/Shell.swift
+++ b/Sources/FoundationExt/Shell.swift
@@ -26,9 +26,9 @@
import Foundation
-struct Shell {
+public struct Shell {
- static func makeExecutable(_ fileName: String) {
+ public static func makeExecutable(_ fileName: String) {
let executableProcess = Process()
executableProcess.launchPath = "/usr/bin/env"
executableProcess.arguments = ["chmod", "755", fileName]
diff --git a/Sources/CommitPrefix/Utilities/String+Extensions.swift b/Sources/FoundationExt/String+Ext.swift
similarity index 98%
rename from Sources/CommitPrefix/Utilities/String+Extensions.swift
rename to Sources/FoundationExt/String+Ext.swift
index db7995c..9a4fac6 100644
--- a/Sources/CommitPrefix/Utilities/String+Extensions.swift
+++ b/Sources/FoundationExt/String+Ext.swift
@@ -26,7 +26,7 @@
import Foundation
-extension String {
+public extension String {
private func findMatches(in string: String, using regex: String) -> [String] {
diff --git a/Tests/CommitPrefixTests/CLInterfaceTests.swift b/Tests/CommitPrefixTests/CLInterfaceTests.swift
new file mode 100644
index 0000000..f0e48b5
--- /dev/null
+++ b/Tests/CommitPrefixTests/CLInterfaceTests.swift
@@ -0,0 +1,321 @@
+//
+// CLInterfaceTests.swift
+// commitPrefix
+//
+// MIT License
+//
+// Copyright (c) 2020 STEPHEN L. MARTINEZ
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+import CLInterface
+import Foundation
+import FoundationExt
+import XCTest
+
+final class CLInterfaceTests: XCTestCase {
+
+ func test_SingleArgumentShortCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil.assertOnProperSingleArg($0.proper)
+ }
+ }
+
+ func test_SingleArgumentLongCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil.assertOnProperSingleArg($0.proper, useLong: true)
+ }
+ }
+
+ func test_FailingSingleArgumentShortCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil.assertOnMalformedSingleArg($0.malformed)
+ }
+ }
+
+ func test_FailingSingleArgumentLongCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil.assertOnMalformedSingleArg($0.malformed, useLong: true)
+ }
+ }
+
+ func test_ExtraArgsSingleArgumentShortCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil.assertOnExtraArgsForSingleArg($0.proper, extraInput: $0.extraInput)
+ }
+ }
+
+ func test_ExtraArgsSingleArgumentLongCommands() {
+ CLITestArgument.singleArgs.forEach {
+ CLITestUtil
+ .assertOnExtraArgsForSingleArg($0.proper, extraInput: $0.extraInput, useLong: true)
+ }
+ }
+
+ // Special Command that uses no args except for the standard default OS argument
+ func test_ViewStateCommand() {
+ let args = ["executable/location"]
+ let argument = CLIArguments(arguments: args)
+ let expectedCommand = UserCommand.viewState
+ let failureMessage = "Expected no input to return an output of"
+ + " \(expectedCommand.description)"
+
+ print("⚖️ ~ Testing no arguments for command \(expectedCommand.description)")
+ func validateCommand(foundCommand: UserCommand) -> Void {
+ let isValid = UserCommand.isEquivelent(lhs: foundCommand, rhs: expectedCommand)
+ XCTAssert(isValid, "\(failureMessage) got \(foundCommand.description) instead")
+ }
+
+ argument.getCommand().do(
+ onFailure: { XCTFail("Input: None \($0.description)") },
+ onSuccess: validateCommand)
+ }
+
+ func test_BranchParseCommand() {
+ let userInputs = "eng"
+ let args = CLITestUtil.makeBranchParseArgument(userInput: [userInputs])
+ let argument = CLIArguments(arguments: args)
+ let expectedCommand = UserCommand.modeBranchParse(validator: userInputs)
+ let failureMessage = "Expected the input of -b \(userInputs) to return to return "
+ + "an output of \(expectedCommand.description)"
+
+ print("⚖️ ~ Testing -b \(userInputs) for command \(expectedCommand.description)")
+ func validateCommand(foundCommand: UserCommand) -> Void {
+ let isValid = UserCommand.isEquivelent(lhs: foundCommand, rhs: expectedCommand)
+ XCTAssert(isValid, "\(failureMessage) got \(foundCommand.description) instead")
+ let validatorsMatch = UserCommand.isValueEquivelent(
+ lhs: foundCommand, rhs: expectedCommand)
+ let foundValidator = foundCommand.associatedValue() ?? "Nil"
+ XCTAssert(validatorsMatch, """
+ Expected found branch validator \(foundValidator) to be \(userInputs)
+ """)
+ }
+
+ argument.getCommand().do(
+ onFailure: { XCTFail("Input: -b \(userInputs) \($0.description)") },
+ onSuccess: validateCommand)
+ }
+
+ func test_ExtraArgsBranchParseCommand() {
+ let userInputs = ["eng", "somethingExtra"]
+ let badInputValue = userInputs.joined(separator: " ")
+ let badArgs = CLITestUtil.makeBranchParseArgument(userInput: userInputs)
+ let badArgument = CLIArguments(arguments: badArgs)
+ let expectedCommand = UserCommand.modeBranchParse(validator: badInputValue)
+ print("⚖️ ~ Testing invalid -b \(badInputValue) for command \(expectedCommand.description)")
+ let badFailureMessage = "Expected the input of -b \(badInputValue) to fail "
+ + "in outputting \(expectedCommand.description)"
+ badArgument.getCommand().do { _ in XCTFail(badFailureMessage) }
+ }
+
+ static var allTests = [
+ ("test_SingleArgumentShortCommands", test_SingleArgumentShortCommands),
+ ("test_SingleArgumentLongCommands", test_SingleArgumentLongCommands),
+ ("test_FailingSingleArgumentShortCommands", test_FailingSingleArgumentShortCommands),
+ ("test_FailingSingleArgumentLongCommands", test_FailingSingleArgumentLongCommands),
+ ("test_ExtraArgsSingleArgumentShortCommands", test_ExtraArgsSingleArgumentShortCommands),
+ ("test_ExtraArgsSingleArgumentLongCommands", test_ExtraArgsSingleArgumentLongCommands),
+ ("test_ViewStateCommand", test_ViewStateCommand),
+ ("test_BranchParseCommand", test_BranchParseCommand),
+ ("test_ExtraArgsBranchParseCommand", test_ExtraArgsBranchParseCommand)
+ ]
+
+}
+
+typealias UserCommand = CLIArguments.UserCommand
+
+typealias CLITestArgument = CLITestUtil.Argument
+
+struct CLITestUtil {
+
+ struct Argument {
+ let short: String
+ let long: String
+ let expected: UserCommand
+
+ struct SingleArgumentPair {
+ let proper: Argument
+ let malformed: Argument
+ let extraInput: String
+ }
+
+ // Correctly Formed Arguments
+ static let version = Self(short: "-v", long: "--version", expected: .outputVersion)
+ static let output = Self(short: "-o", long: "--output", expected: .outputPrefixes)
+ static let delete = Self(short: "-d", long: "--delete", expected: .deletePrefixes)
+ static let normal = Self(short: "-n", long: "--normal", expected: .modeNormal)
+
+ // Malformed Arguments
+ static let badVersion = Self(short: "-cv", long: "--verzion", expected: .outputVersion)
+ static let badOutput = Self(short: "-op", long: "--ouput", expected: .outputPrefixes)
+ static let badDelete = Self(short: "-ds", long: "--dekete", expected: .deletePrefixes)
+ static let badNormal = Self(short: "-nm", long: "--notmal", expected: .modeNormal)
+
+ static let singleArgs: [SingleArgumentPair] = [
+ .init(proper: .version, malformed: .badVersion, extraInput: "need me some version"),
+ .init(proper: .output, malformed: .badOutput, extraInput: "show me the raw output"),
+ .init(proper: .delete, malformed: .badDelete, extraInput: "delete this prefix"),
+ .init(proper: .normal, malformed: .badNormal, extraInput: "get me to normal mode")
+ ]
+
+ }
+
+ static func makeBranchParseArgument(
+ useLong: Bool = false,
+ userInput: [String]
+ ) -> [String] {
+ let argList = ["executable/location", (useLong ? "--branchParse" : "-b")]
+ return argList + userInput
+ }
+
+ static func makeArgument(
+ _ arg: Argument,
+ useLong: Bool = false,
+ userInput: String? = nil
+ ) -> [String] {
+ let argList = ["executable/location", (useLong ? arg.long : arg.short)]
+ guard let userInput = userInput else { return argList }
+ return argList + [userInput]
+ }
+
+ static func assertOnProperSingleArg(_ command: Argument, useLong: Bool = false) {
+ let inputValue = useLong ? command.long : command.short
+ print("⚖️ ~ Testing \(inputValue) for command \(command.expected.description)")
+ let argument = CLIArguments(arguments: makeArgument(command, useLong: useLong))
+ let failureMessage = """
+ Expected the input of \(inputValue) to return an output of \(command.expected.description)
+ """
+ func validateCommand(foundCommand: UserCommand) -> Void {
+ guard UserCommand.isEquivelent(lhs: foundCommand, rhs: command.expected) else {
+ XCTFail(failureMessage)
+ return
+ }
+ }
+ argument.getCommand().do(
+ onFailure: { XCTFail("Input: \(inputValue) " + $0.description) },
+ onSuccess: validateCommand)
+ }
+
+ static func assertOnMalformedSingleArg(_ command: Argument, useLong: Bool = false) {
+ let badInputValue = useLong ? command.long : command.short
+ print("⚖️ ~ Testing invalid \(badInputValue) for command \(command.expected.description)")
+ let badArgument = CLIArguments(arguments: makeArgument(command, useLong: useLong))
+ let badFailureMessage = """
+ Expected the input of \(badInputValue) to fail in outputting \(command.expected.description)
+ """
+ badArgument.getCommand().do { _ in XCTFail(badFailureMessage) }
+ }
+
+ static func assertOnExtraArgsForSingleArg(
+ _ command: Argument,
+ extraInput: String,
+ useLong: Bool = false
+ ) {
+ let inputValue = useLong ? command.long : command.short
+ let testingMessage = "⚖️ ~ Testing invalid extra args \(inputValue) \(extraInput)"
+ + " for command \(command.expected.description)"
+ print(testingMessage)
+ let badArgument = CLIArguments(
+ arguments: makeArgument(command, useLong: useLong, userInput: extraInput))
+ let badFailureMessage = "Expected the input of \(inputValue) \(extraInput)"
+ + "to fail in outputting \(command.expected.description)"
+ badArgument.getCommand().do { _ in XCTFail(badFailureMessage) }
+ }
+
+}
+
+extension CLIError {
+
+ var description: String {
+ switch self {
+ case .commandNotRecognized:
+ return "CLIError: Command Not Recognized"
+ case .tooManyArguments:
+ return "CLIError: Too Many Arguments"
+ case .emptyEntry:
+ return "CLIError: Empty Entry"
+ case .invalidEntryFormat:
+ return "CLIError: Invalid Entry Format"
+ case .unexpectedError:
+ return "CLIError: Unexpected Error"
+ }
+ }
+
+}
+
+extension UserCommand {
+
+ var description: String {
+ switch self {
+ case .outputVersion:
+ return "outputVersion"
+ case .viewState:
+ return "viewState"
+ case .outputPrefixes:
+ return "outputPrefixes"
+ case .deletePrefixes:
+ return "deletePrefixes"
+ case .modeNormal:
+ return "modeNormal"
+ case let .modeBranchParse(validator):
+ return "modeBranchParse \(validator)"
+ case let .newPrefixes(value):
+ return "newPrefixes \(value)"
+ }
+
+ }
+
+ static func isEquivelent(lhs: UserCommand, rhs: UserCommand) -> Bool {
+ switch (lhs, rhs) {
+ case (.outputVersion, .outputVersion),
+ (.viewState, .viewState),
+ (.outputPrefixes, .outputPrefixes),
+ (.deletePrefixes, .deletePrefixes),
+ (.modeNormal, .modeNormal),
+ (.modeBranchParse, .modeBranchParse),
+ (.newPrefixes, .newPrefixes):
+ return true
+ default:
+ return false
+ }
+
+ }
+
+ static func isValueEquivelent(lhs: UserCommand, rhs: UserCommand) -> Bool {
+ switch (lhs, rhs) {
+ case let (.modeBranchParse(lhsValue), .modeBranchParse(rhsValue)):
+ return lhsValue == rhsValue
+ case let (.newPrefixes(lhsValue), .newPrefixes(rhsValue)):
+ return lhsValue == rhsValue
+ default:
+ return false
+ }
+ }
+
+ func associatedValue() -> String? {
+ switch self {
+ case let .modeBranchParse(validator: value):
+ return value
+ case let .newPrefixes(value: value):
+ return value
+ default:
+ return nil
+ }
+ }
+}
diff --git a/Tests/CommitPrefixTests/CommitPrefixTests.swift b/Tests/CommitPrefixTests/CommitPrefixTests.swift
index a923b14..edbbbaf 100644
--- a/Tests/CommitPrefixTests/CommitPrefixTests.swift
+++ b/Tests/CommitPrefixTests/CommitPrefixTests.swift
@@ -24,51 +24,50 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+import CLInterface
+import Foundation
import XCTest
-import class Foundation.Bundle
final class commitPrefixTests: XCTestCase {
- func testExample() throws {
- // This is an example of a functional test case.
- // Use XCTAssert and related functions to verify your tests produce the correct
- // results.
+
+ func test_wholeApplication() throws {
- // Some of the APIs that we use below are available in macOS 10.13 and above.
- guard #available(macOS 10.13, *) else {
- return
- }
+ guard #available(macOS 10.15, *) else { return }
- let fooBinary = productsDirectory.appendingPathComponent("commitPrefix")
+ let commitPrefixBinary = TestUtilities.urlOfExecutable(named: "commitPrefix")
let process = Process()
- process.executableURL = fooBinary
+ process.executableURL = commitPrefixBinary
let pipe = Pipe()
process.standardOutput = pipe
+ let errorPipe = Pipe()
+ process.standardError = errorPipe
+
try process.run()
process.waitUntilExit()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output = String(data: data, encoding: .utf8)
- XCTAssert(false, "Tests have not been implemented")
- //XCTAssertEqual(output, "Hello, world!\n")
- }
+ let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
+ let errorOutput = String(data: errorData, encoding: .utf8)
- /// Returns path to the built products directory.
- var productsDirectory: URL {
- #if os(macOS)
- for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
- return bundle.bundleURL.deletingLastPathComponent()
- }
- fatalError("couldn't find the products directory")
- #else
- return Bundle.main.bundleURL
- #endif
+ let noOutput = output == nil || output == ""
+ let validStdErrOutput = (errorOutput ?? "")
+ .contains("Error: Not in a git repo or at the root of one")
+
+ XCTAssert(noOutput, """
+ commitPrefix should only return stderr output at this point. A full mock file \
+ environment has not been set up yet.
+ """)
+
+ XCTAssert(validStdErrOutput, "A CPError.notAGitRepo should be thrown here")
}
static var allTests = [
- ("testExample", testExample),
+ ("test_wholeApplication", test_wholeApplication)
]
+
}
diff --git a/Tests/CommitPrefixTests/XCTestManifests.swift b/Tests/CommitPrefixTests/TestUtilities.swift
similarity index 65%
rename from Tests/CommitPrefixTests/XCTestManifests.swift
rename to Tests/CommitPrefixTests/TestUtilities.swift
index d56b574..d3879c8 100644
--- a/Tests/CommitPrefixTests/XCTestManifests.swift
+++ b/Tests/CommitPrefixTests/TestUtilities.swift
@@ -1,5 +1,5 @@
//
-// XCTestManifests.swift
+// TestUtilities.swift
// commitPrefix
//
// MIT License
@@ -24,12 +24,25 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
+import Foundation
import XCTest
-#if !canImport(ObjectiveC)
-public func allTests() -> [XCTestCaseEntry] {
- return [
- testCase(commitPrefixTests.allTests),
- ]
+struct TestUtilities {
+
+ static func urlOfExecutable(named executableName: String) -> URL {
+ productsDirectory.appendingPathComponent(executableName)
+ }
+
+ /// Returns path to the built products directory.
+ private static var productsDirectory: URL {
+ #if os(macOS)
+ for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") {
+ return bundle.bundleURL.deletingLastPathComponent()
+ }
+ fatalError("Could not find the products directory")
+ #else
+ return Bundle.main.bundleURL
+ #endif
+ }
+
}
-#endif
diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift
index 0f66cdf..7de07b2 100644
--- a/Tests/LinuxMain.swift
+++ b/Tests/LinuxMain.swift
@@ -25,9 +25,11 @@
// SOFTWARE.
import XCTest
-
-import commitPrefixTests
+@testable import CommitPrefixTests
var tests = [XCTestCaseEntry]()
-tests += commitPrefixTests.allTests()
+
+tests += commitPrefixTests.allTests
+tests += CLInterfaceTests.allTests
+
XCTMain(tests)