diff --git a/Package.swift b/Package.swift index 5dff1bb..8087301 100644 --- a/Package.swift +++ b/Package.swift @@ -68,6 +68,13 @@ let package = Package( "NIO" ] ), + .testTarget( + name: "ExecutorMocksTests", + dependencies: [ + "ExecutorMocks", + "CommandKit" + ] + ), .testTarget( name: "CommandKitTests", dependencies: [ diff --git a/Sources/CommandKit/Commands/File+Cmd.swift b/Sources/CommandKit/Commands/File+Cmd.swift index a319475..0cf89fc 100644 --- a/Sources/CommandKit/Commands/File+Cmd.swift +++ b/Sources/CommandKit/Commands/File+Cmd.swift @@ -9,9 +9,9 @@ extension Cmd { /// - Parameter relativePath: Relative path to be converted into full public func pwd(relativePath path: String? = nil) -> EventLoopFuture { if let path = path { - return shell.run(bash: "TMP_P=$(pwd) && cd \(path.quoteEscape) && pwd && cd \"$TMP_P\"").future.trimMap() + return shell.run(bash: "TMP_P=$(pwd) && cd \(path.quoteEscape) && pwd && cd \"$TMP_P\"", output: nil).future.trimMap() } else { - return shell.run(bash: "pwd").future.trimMap() + return shell.run(bash: "pwd", output: nil).future.trimMap() } } @@ -31,13 +31,13 @@ extension Cmd { /// Return a command path if exists /// - Parameter command: Command public func which(_ command: String) -> EventLoopFuture { - return shell.run(bash: "which \(command)").future.trimMap() + return shell.run(bash: "which \(command)", output: nil).future.trimMap() } /// Check is folder is empty /// - Parameter path: Command public func isEmpty(path: String) -> EventLoopFuture { - return shell.run(bash: "[ '$(ls -A /path/to/directory)' ] && echo 'not empty' || echo 'empty'").future.map { output in + return shell.run(bash: "[ '$(ls -A /path/to/directory)' ] && echo 'not empty' || echo 'empty'", output: nil).future.map { output in return output.trimmingCharacters(in: .whitespacesAndNewlines) == "empty" } } @@ -51,7 +51,7 @@ extension Cmd { /// Return content of a file as a string /// - Parameter path: Path to file public func cat(path: String) -> EventLoopFuture { - return shell.run(bash: "cat \(path.quoteEscape)").future + return shell.run(bash: "cat \(path.quoteEscape)", output: nil).future } /// List files in a path @@ -59,8 +59,11 @@ extension Cmd { /// - Parameter flags: Flags /// - Parameter output: Future public func ls(path: String, flags: FlagsConvertible? = nil, output: ((String) -> ())? = nil) -> EventLoopFuture { - let flags = flags?.flags ?? "" - return shell.run(bash: "ls \(flags) \(path.quoteEscape)", output: output).future + var flags = flags?.flags ?? "" + if !flags.isEmpty { + flags.append(contentsOf: " ") + } + return shell.run(bash: "ls \(flags)\(path.quoteEscape)", output: output).future } /// Remove flags diff --git a/Sources/CommandKit/Commands/Platform+Cmd.swift b/Sources/CommandKit/Commands/Platform+Cmd.swift index 7489c02..aa1c0c7 100644 --- a/Sources/CommandKit/Commands/Platform+Cmd.swift +++ b/Sources/CommandKit/Commands/Platform+Cmd.swift @@ -49,7 +49,7 @@ extension Cmd { /// Get target platform public func platform() -> EventLoopFuture { - return shell.run(bash: Os.command).future.trimMap() + return shell.run(bash: Os.command, output: nil).future.trimMap() } } diff --git a/Sources/CommandKit/Commands/System+Cmd.swift b/Sources/CommandKit/Commands/System+Cmd.swift index 25423f3..9c3e03d 100644 --- a/Sources/CommandKit/Commands/System+Cmd.swift +++ b/Sources/CommandKit/Commands/System+Cmd.swift @@ -6,7 +6,7 @@ extension Cmd { /// Who Am I (whoami) public func whoami() -> EventLoopFuture { - return shell.run(bash: "whoami").future.trimMap() + return shell.run(bash: "whoami", output: nil).future.trimMap() } } diff --git a/Sources/CommandKit/Commands/Tools+Install.swift b/Sources/CommandKit/Commands/Tools+Install.swift index 50bfd06..032de33 100644 --- a/Sources/CommandKit/Commands/Tools+Install.swift +++ b/Sources/CommandKit/Commands/Tools+Install.swift @@ -6,7 +6,7 @@ extension Install { /// Install HomeBrew /// - Note: macOS only public func brew() -> EventLoopFuture { - return shell.run(bash: "/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"").future.void() + return shell.run(bash: "/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"", output: nil).future.void() } /// Install cURL @@ -27,9 +27,9 @@ extension Install { return shell.cmd.os().flatMap { os in switch os { case .macOs: - return self.shell.run(bash: "brew install \(name)").future.void() + return self.shell.run(bash: "brew install \(name)", output: nil).future.void() case .linux: - return self.shell.run(bash: "sudo apt-get install \(name)").future.void() + return self.shell.run(bash: "sudo apt-get install \(name)", output: nil).future.void() default: return self.shell.eventLoop.makeFailedFuture(Cmd.CmdError.unsupportedPlatform) } @@ -42,7 +42,7 @@ extension Install { return shell.cmd.os().flatMap { os in switch os { case .macOs: - return self.shell.run(bash: "brew install einstore/homebrew-tap/systemator").future.void() + return self.shell.run(bash: "brew install einstore/homebrew-tap/systemator", output: nil).future.void() default: return self.shell.eventLoop.makeFailedFuture(Cmd.CmdError.unsupportedPlatform) } diff --git a/Sources/CommandKit/Property categories/Cmd.swift b/Sources/CommandKit/Property categories/Cmd.swift index a35181f..dd5c3aa 100644 --- a/Sources/CommandKit/Property categories/Cmd.swift +++ b/Sources/CommandKit/Property categories/Cmd.swift @@ -1,4 +1,5 @@ import Foundation +import ExecutorKit import WebErrorKit @@ -15,16 +16,16 @@ public struct Cmd { } - let shell: Shell + let shell: MasterExecutor - init(_ shell: Shell) { + init(_ shell: MasterExecutor) { self.shell = shell } } -extension Shell { +extension MasterExecutor { public var cmd: Cmd { return Cmd(self) diff --git a/Sources/CommandKit/Property categories/Install.swift b/Sources/CommandKit/Property categories/Install.swift index 667adea..70b0681 100644 --- a/Sources/CommandKit/Property categories/Install.swift +++ b/Sources/CommandKit/Property categories/Install.swift @@ -1,4 +1,5 @@ import WebErrorKit +import ExecutorKit /// Extension with commands @@ -13,9 +14,9 @@ public struct Install { } - let shell: Shell + let shell: MasterExecutor - init(_ shell: Shell) { + init(_ shell: MasterExecutor) { self.shell = shell } diff --git a/Sources/ExecutorKit/Executor.swift b/Sources/ExecutorKit/Executor.swift index 3458816..d1b90ca 100644 --- a/Sources/ExecutorKit/Executor.swift +++ b/Sources/ExecutorKit/Executor.swift @@ -5,6 +5,10 @@ import NIO /// Executor protocol public protocol Executor { + var output: ((String) -> ())? { get set } + + var eventLoop: EventLoop { get } + /// Run bash command /// - Parameter bash: bash command /// - Parameter output: Closure to output console text diff --git a/Sources/ExecutorMocks/ExecutorMock.swift b/Sources/ExecutorMocks/ExecutorMock.swift index 09809a5..5b3d0b4 100644 --- a/Sources/ExecutorMocks/ExecutorMock.swift +++ b/Sources/ExecutorMocks/ExecutorMock.swift @@ -1,22 +1,29 @@ import Foundation import ExecutorKit +import ShellKit -public class ExecutorMock: Executor { +public class ExecutorMock: MasterExecutor { + + public var executor: Executor { + return self + } + + public var output: ((String) -> ())? = nil /// Event loop on which all commands execute - public var eventLoop = EmbeddedEventLoop() + public var eventLoop: EventLoop = EmbeddedEventLoop() - /// Responses registered for commands + /// Commads registered for commands /// - Note: Format is [Command: [Output piece]] - public var responses: [String: [String]] = [:] + public var mockResults: [String: [String]] = [:] - /// Errors for responses that are supposed to fail - public var failingResponses: [String: Error] = [:] + /// Errors for results that are supposed to fail + public var failingMockResults: [String: Error] = [:] public func run(bash: String, output: ((String) -> ())?) -> ProcessFuture { - guard let response = responses[bash] else { - guard let error = failingResponses[bash] else { + guard let response = mockResults[bash] else { + guard let error = failingMockResults[bash] else { fatalError("[ShellKit] Missing mock response for:\n\(bash)\n\n") } let f: EventLoopFuture = eventLoop.makeFailedFuture(error) @@ -80,6 +87,8 @@ public class ExecutorMock: Executor { return upload(data: string.data(using: .utf8)!, to: path) } + public init() { } + } diff --git a/Sources/ExecutorMocks/Exports.swift b/Sources/ExecutorMocks/Exports.swift new file mode 100644 index 0000000..44d3b5b --- /dev/null +++ b/Sources/ExecutorMocks/Exports.swift @@ -0,0 +1,2 @@ +@_exported import CommandKit +@_exported import ExecutorKit diff --git a/Sources/LocalShell/LocalExecutor.swift b/Sources/LocalShell/LocalExecutor.swift index 5c8eb52..255fac4 100644 --- a/Sources/LocalShell/LocalExecutor.swift +++ b/Sources/LocalShell/LocalExecutor.swift @@ -5,6 +5,8 @@ import NIO public class LocalExecutor: Executor { + public var output: ((String) -> ())? = nil + public enum LocalExecutorError: SerializableWebError { case processFailed(exit: Int32) @@ -22,7 +24,7 @@ public class LocalExecutor: Executor { } - let eventLoop: EventLoop + public let eventLoop: EventLoop public private(set) var currentDirectoryPath: String let identifier: String = UUID().uuidString @@ -92,6 +94,7 @@ public class LocalExecutor: Executor { if let text = data.map({ String(decoding: $0, as: Unicode.UTF8.self) }), !text.isEmpty { outputText.append(text) output?(text) + self.output?(text) print(text, terminator: "") } } diff --git a/Sources/SSHShell/Exports.swift b/Sources/SSHShell/Exports.swift index 102497a..86af7c2 100644 --- a/Sources/SSHShell/Exports.swift +++ b/Sources/SSHShell/Exports.swift @@ -1,6 +1,4 @@ -@_exported import Foundation -@_exported import protocol NIO.EventLoop -@_exported import class NIO.EventLoopFuture +@_exported import ExecutorKit @_exported import protocol Shout.SSHAuthMethod @_exported import struct Shout.SSHPassword @_exported import struct Shout.SSHAgent diff --git a/Sources/SSHShell/SSHExecutor.swift b/Sources/SSHShell/SSHExecutor.swift index 794a683..8e45734 100644 --- a/Sources/SSHShell/SSHExecutor.swift +++ b/Sources/SSHShell/SSHExecutor.swift @@ -6,7 +6,9 @@ import NIO /// SSH executor public class SSHExecutor: Executor { - let eventLoop: EventLoop + public var output: ((String) -> ())? + + public let eventLoop: EventLoop let ssh: SSH var sftp: SFTP? @@ -38,6 +40,7 @@ public class SSHExecutor: Executor { outputText += text self.eventLoop.execute { output?(text) + self.output?(text) } } if res == 0 { diff --git a/Sources/ShellKit/Extensions/File.swift b/Sources/ShellKit/Extensions/File.swift deleted file mode 100644 index fecc4ab..0000000 --- a/Sources/ShellKit/Extensions/File.swift +++ /dev/null @@ -1 +0,0 @@ -import Foundation diff --git a/Sources/ShellKit/MasterExecutor.swift b/Sources/ShellKit/MasterExecutor.swift new file mode 100644 index 0000000..35c8216 --- /dev/null +++ b/Sources/ShellKit/MasterExecutor.swift @@ -0,0 +1,8 @@ +import ExecutorKit + + +public protocol MasterExecutor: Executor { + + var executor: Executor { get } + +} diff --git a/Sources/ShellKit/Shell.swift b/Sources/ShellKit/Shell.swift index f7741e6..40d5606 100644 --- a/Sources/ShellKit/Shell.swift +++ b/Sources/ShellKit/Shell.swift @@ -3,7 +3,7 @@ import NIO /// Main executor -public class Shell: Executor { +public class Shell: MasterExecutor { /// Connection type public struct Connection { diff --git a/Tests/ExecutorMocksTests/Tests.swift b/Tests/ExecutorMocksTests/Tests.swift new file mode 100644 index 0000000..6f785f7 --- /dev/null +++ b/Tests/ExecutorMocksTests/Tests.swift @@ -0,0 +1,32 @@ +import XCTest +import ExecutorMocks +import CommandKit +import NIO + + +class Tests: XCTestCase { + + var shell: ExecutorMock! + + override func setUp() { + super.setUp() + + shell = try! ExecutorMock() + } + + func testBasicMock() { + let hu = "huhuhuhu :)" + shell.mockResults["najs woe"] = [hu] + let out = try! shell.run(bash: "najs woe", output: nil).wait() + XCTAssertEqual(out, hu) + } + + func testCommandMock() { + shell.mockResults["ls /tmp"] = [ + "file1" + ] + let out = try! shell.cmd.ls(path: "/tmp").wait() + XCTAssertEqual(out, "file1") + } + +}