Implemented the RunProcessUseCase use case in the app target.

This commit is contained in:
Javier Cicchelli 2024-10-05 10:30:54 +02:00
parent 12177be8db
commit 66844fe389
3 changed files with 160 additions and 0 deletions

View File

@ -35,6 +35,7 @@
membershipExceptions = (
UITests/UITests.swift,
UITests/UITestsLaunchTests.swift,
UnitTests/Tests/UseCases/RunProcessUseCaseTests.swift,
UnitTests/UnitTests.swift,
);
target = 46D4BE762CB06ED300FCFB84 /* Piper */;
@ -42,6 +43,7 @@
46D4BEF72CB07CCB00FCFB84 /* Exceptions for "Tests" folder in "UnitTests" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
UnitTests/Tests/UseCases/RunProcessUseCaseTests.swift,
UnitTests/UnitTests.swift,
);
target = 46D4BED32CB07C7A00FCFB84 /* UnitTests */;
@ -492,6 +494,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 7FMNM89WKG;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.rock-n-code.piper.tests.unit";
PRODUCT_NAME = "$(TARGET_NAME)";
@ -509,6 +512,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 7FMNM89WKG;
GENERATE_INFOPLIST_FILE = YES;
MACOSX_DEPLOYMENT_TARGET = 14.6;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "com.rock-n-code.piper.tests.unit";
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@ -0,0 +1,87 @@
//
// RunProcessUseCase.swift
// Piper ~ App
//
// Created by Javier Cicchelli on 05/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Foundation
struct RunProcessUseCase {
// MARK: Type aliases
typealias Output = String
// MARK: Properties
private let path: String
private let arguments: [String]?
// MARK: Initialisers
init(
path: String,
arguments: [String] = []
) {
self.path = path
self.arguments = arguments
}
// MARK: Functions
func callAsFunction() async throws -> Output {
let process = Process()
process.executableURL = URL(fileURLWithPath: path)
process.arguments = arguments
let pipeError = Pipe()
let pipeOutput = Pipe()
process.standardError = pipeError
process.standardOutput = pipeOutput
async let streamOutput = pipeOutput.availableData.append()
async let streamError = pipeError.availableData.append()
do {
try process.run()
let dataOutput = await streamOutput
let dataError = await streamError
guard dataError.isEmpty else {
guard let errorOutput = String(data: dataError, encoding: .utf8) else {
throw RunProcessError.unexpected
}
throw RunProcessError.output(errorOutput)
}
guard let output = String(data: dataOutput, encoding: .utf8) else {
throw RunProcessError.unexpected
}
return await withCheckedContinuation { continuation in
process.terminationHandler = { _ in
continuation.resume(returning: output)
}
}
} catch let error as RunProcessError {
throw error
} catch {
throw RunProcessError.captured(error)
}
}
}
// MARK: - Errors
enum RunProcessError: Error {
case captured(_ error: Error)
case output(_ output: String)
case unexpected
}

View File

@ -0,0 +1,69 @@
//
// RunProcessUseCaseTests.swift
// UnitTests
//
// Created by Javier Cicchelli on 05/10/2024.
// Copyright © 2024 Röck+Cöde. All rights reserved.
//
import Testing
@testable import Piper
struct RunProcessUseCaseTests {
// MARK: Functions tests
@Test("Run a simple process without arguments")
func simpleCommand_withoutArguments() async throws {
// GIVEN
let useCase = RunProcessUseCase(path: .ls)
// WHEN
let output = try await useCase()
// THEN
#expect(output.isEmpty == false)
}
@Test("Run a simple process with arguments")
func simpleCommand_withArguments() async throws {
// GIVEN
let useCase = RunProcessUseCase(
path: .ls,
arguments: ["-la", "."]
)
// WHEN
let output = try await useCase()
// THEN
#expect(output.isEmpty == false)
}
@Test("Run a simple command with arguments that throws an error")
func simpleCommand_withArguments_throwsError() async throws {
// GIVEN
let useCase = RunProcessUseCase(
path: .ls,
arguments: ["-la", "~"]
)
// WHEN
// THEN
await #expect(throws: RunProcessError.self) {
try await useCase()
}
}
}
// MARK: - String+Constants
private extension String {
// MARK: Constants
static let ls = "/bin/ls"
}