2
0
Fork 0
mirror of https://github.com/mas-cli/mas synced 2025-03-06 23:57:21 +00:00

Improve value, error & captured stream comparisons.

Partial 

Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
This commit is contained in:
Ross Goldberg 2024-12-31 22:56:41 -05:00
parent f49d63a4f3
commit 905c4f7982
No known key found for this signature in database
24 changed files with 299 additions and 232 deletions

View file

@ -9,4 +9,5 @@ disabled_rules:
- force_cast
- force_try
- implicitly_unwrapped_optional
- large_tuple
- no_magic_numbers

View file

@ -11,19 +11,15 @@ import Quick
@testable import mas
/// Deprecated test.
public final class AccountSpec: QuickSpec {
override public func spec() {
beforeSuite {
MAS.initialize()
}
// account command disabled since macOS 12 Monterey https://github.com/mas-cli/mas#known-issues
describe("Account command") {
it("displays active account") {
expect {
try MAS.Account.parse([]).run()
}
.to(throwError(MASError.notSupported))
describe("account command") {
it("displays not supported warning") {
expect(consequencesOf(try MAS.Account.parse([]).run()))
== (error: MASError.notSupported, stdout: "", stderr: "")
}
}
}

View file

@ -18,10 +18,8 @@ public final class HomeSpec: QuickSpec {
}
describe("home command") {
it("can't find app with unknown ID") {
expect {
try MAS.Home.parse(["999"]).run(searcher: MockAppStoreSearcher())
}
.to(throwError(MASError.unknownAppID(999)))
expect(consequencesOf(try MAS.Home.parse(["999"]).run(searcher: MockAppStoreSearcher())))
== (MASError.unknownAppID(999), "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -19,10 +18,8 @@ public final class InfoSpec: QuickSpec {
}
describe("Info command") {
it("can't find app with unknown ID") {
expect {
try MAS.Info.parse(["999"]).run(searcher: MockAppStoreSearcher())
}
.to(throwError(MASError.unknownAppID(999)))
expect(consequencesOf(try MAS.Info.parse(["999"]).run(searcher: MockAppStoreSearcher())))
== (MASError.unknownAppID(999), "", "")
}
it("displays app details") {
let mockResult = SearchResult(
@ -36,21 +33,25 @@ public final class InfoSpec: QuickSpec {
trackViewUrl: "https://awesome.app",
version: "1.0"
)
expect {
try captureStream(stdout) {
expect(
consequencesOf(
try MAS.Info.parse([String(mockResult.trackId)])
.run(searcher: MockAppStoreSearcher([mockResult.trackId: mockResult]))
}
}
== """
Awesome App 1.0 [$2.00]
By: Awesome Dev
Released: 2019-01-07
Minimum OS: 10.14
Size: 1 KB
From: https://awesome.app
)
)
== (
nil,
"""
Awesome App 1.0 [$2.00]
By: Awesome Dev
Released: 2019-01-07
Minimum OS: 10.14
Size: 1 KB
From: https://awesome.app
"""
""",
""
)
}
}
}

View file

@ -18,10 +18,12 @@ public final class InstallSpec: QuickSpec {
}
xdescribe("install command") {
it("installs apps") {
expect {
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
}
.toNot(throwError())
expect(
consequencesOf(
try MAS.Install.parse([]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
)
)
== (nil, "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -19,12 +18,8 @@ public final class ListSpec: QuickSpec {
}
describe("list command") {
it("lists apps") {
expect {
try captureStream(stderr) {
try MAS.List.parse([]).run(appLibrary: MockAppLibrary())
}
}
== "Error: No installed apps found\n"
expect(consequencesOf(try MAS.List.parse([]).run(appLibrary: MockAppLibrary())))
== (nil, "", "Error: No installed apps found\n")
}
}
}

View file

@ -21,10 +21,10 @@ public final class LuckySpec: QuickSpec {
}
xdescribe("lucky command") {
it("installs the first app matching a search") {
expect {
try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher)
}
.toNot(throwError())
expect(
consequencesOf(try MAS.Lucky.parse(["Slack"]).run(appLibrary: MockAppLibrary(), searcher: searcher))
)
== (nil, "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2019 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -19,10 +18,8 @@ public final class OpenSpec: QuickSpec {
}
describe("open command") {
it("can't find app with unknown ID") {
expect {
try MAS.Open.parse(["999"]).run(searcher: MockAppStoreSearcher())
}
.to(throwError(MASError.unknownAppID(999)))
expect(consequencesOf(try MAS.Open.parse(["999"]).run(searcher: MockAppStoreSearcher())))
== (MASError.unknownAppID(999), "", "")
}
}
}

View file

@ -32,8 +32,8 @@ public final class OutdatedSpec: QuickSpec {
version: "1.28"
)
expect {
try captureStream(stdout) {
expect(
consequencesOf(
try MAS.Outdated.parse([])
.run(
appLibrary: MockAppLibrary(
@ -47,9 +47,9 @@ public final class OutdatedSpec: QuickSpec {
),
searcher: MockAppStoreSearcher([mockSearchResult.trackId: mockSearchResult])
)
}
}
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"
)
)
== (nil, "490461369 Bandwidth+ (1.27 -> 1.28)\n", "")
}
}
}

View file

@ -18,10 +18,13 @@ public final class PurchaseSpec: QuickSpec {
}
xdescribe("purchase command") {
it("purchases apps") {
expect {
try MAS.Purchase.parse(["999"]).run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
}
.toNot(throwError())
expect(
consequencesOf(
try MAS.Purchase.parse(["999"])
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
)
)
== (nil, "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -25,19 +24,17 @@ public final class SearchSpec: QuickSpec {
trackViewUrl: "mas preview url",
version: "0.0"
)
expect {
try captureStream(stdout) {
expect(
consequencesOf(
try MAS.Search.parse(["slack"])
.run(searcher: MockAppStoreSearcher([mockResult.trackId: mockResult]))
}
}
== " 1111 slack (0.0)\n"
)
)
== (nil, " 1111 slack (0.0)\n", "")
}
it("fails when searching for nonexistent app") {
expect {
try MAS.Search.parse(["nonexistent"]).run(searcher: MockAppStoreSearcher())
}
.to(throwError(MASError.noSearchResultsFound))
expect(consequencesOf(try MAS.Search.parse(["nonexistent"]).run(searcher: MockAppStoreSearcher())))
== (MASError.noSearchResultsFound, "", "")
}
}
}

View file

@ -17,13 +17,10 @@ public final class SignInSpec: QuickSpec {
beforeSuite {
MAS.initialize()
}
// signin command disabled since macOS 10.13 High Sierra: https://github.com/mas-cli/mas#known-issues
describe("signin command") {
it("signs in") {
expect {
try MAS.SignIn.parse(["", ""]).run()
}
.to(throwError(MASError.notSupported))
expect(consequencesOf(try MAS.SignIn.parse(["", ""]).run()))
== (MASError.notSupported, "", "")
}
}
}

View file

@ -18,10 +18,8 @@ public final class SignOutSpec: QuickSpec {
}
describe("signout command") {
it("signs out") {
expect {
try MAS.SignOut.parse([]).run()
}
.toNot(throwError())
expect(consequencesOf(try MAS.SignOut.parse([]).run()))
== (nil, "", "")
}
}
}

View file

@ -31,46 +31,30 @@ public final class UninstallSpec: QuickSpec {
let uninstall = try! MAS.Uninstall.parse(["--dry-run", String(appID)])
it("can't remove a missing app") {
expect {
try uninstall.run(appLibrary: MockAppLibrary())
}
.to(throwError(MASError.notInstalled(appID: appID)))
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary())))
== (MASError.notInstalled(appID: appID), "", "")
}
it("finds an app") {
expect {
try captureStream(stdout) {
try uninstall.run(appLibrary: MockAppLibrary(app))
}
}
== "==> 'Some App' '/tmp/Some.app'\n==> (not removed, dry run)\n"
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(app))))
== (nil, "==> 'Some App' '/tmp/Some.app'\n==> (not removed, dry run)\n", "")
}
}
context("wet run") {
let uninstall = try! MAS.Uninstall.parse([String(appID)])
it("can't remove a missing app") {
expect {
try uninstall.run(appLibrary: MockAppLibrary())
}
.to(throwError(MASError.notInstalled(appID: appID)))
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary())))
== (MASError.notInstalled(appID: appID), "", "")
}
it("removes an app") {
expect {
try captureStream(stdout) {
try uninstall.run(appLibrary: MockAppLibrary(app))
}
}
.toNot(throwError())
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(app))))
== (nil, "", "")
}
it("fails if there is a problem with the trash command") {
var brokenApp = app
brokenApp.bundlePath = "/dev/null"
expect {
try captureStream(stdout) {
try uninstall.run(appLibrary: MockAppLibrary(brokenApp))
}
}
.to(throwError(MASError.uninstallFailed(error: nil)))
expect(consequencesOf(try uninstall.run(appLibrary: MockAppLibrary(brokenApp))))
== (MASError.uninstallFailed(error: nil), "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -19,13 +18,13 @@ public final class UpgradeSpec: QuickSpec {
}
describe("upgrade command") {
it("finds no upgrades") {
expect {
try captureStream(stderr) {
expect(
consequencesOf(
try MAS.Upgrade.parse([])
.run(appLibrary: MockAppLibrary(), searcher: MockAppStoreSearcher())
}
}
.toNot(throwError())
)
)
== (nil, "", "")
}
}
}

View file

@ -18,10 +18,8 @@ public final class VendorSpec: QuickSpec {
}
describe("vendor command") {
it("can't find app with unknown ID") {
expect {
try MAS.Vendor.parse(["999"]).run(searcher: MockAppStoreSearcher())
}
.to(throwError(MASError.unknownAppID(999)))
expect(consequencesOf(try MAS.Vendor.parse(["999"]).run(searcher: MockAppStoreSearcher())))
== (MASError.unknownAppID(999), "", "")
}
}
}

View file

@ -6,7 +6,6 @@
// Copyright © 2018 mas-cli. All rights reserved.
//
import Foundation
import Nimble
import Quick
@ -19,12 +18,8 @@ public final class VersionSpec: QuickSpec {
}
describe("version command") {
it("displays the current version") {
expect {
try captureStream(stdout) {
try MAS.Version.parse([]).run()
}
}
== "\(Package.version)\n"
expect(consequencesOf(try MAS.Version.parse([]).run()))
== (nil, "\(Package.version)\n", "")
}
}
}

View file

@ -18,26 +18,41 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
}
describe("url string") {
it("contains the search term") {
expect {
ITunesSearchAppStoreSearcher()
.searchURL(
for: "myapp",
inRegion: findISORegion(forAlpha2Code: "US")
)?
.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp"
expect(
consequencesOf(
ITunesSearchAppStoreSearcher()
.searchURL(
for: "myapp",
inRegion: findISORegion(forAlpha2Code: "US")
)?
.absoluteString
)
)
== (
"https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=myapp",
nil,
"",
""
)
}
it("contains the encoded search term") {
expect {
ITunesSearchAppStoreSearcher()
.searchURL(
for: "My App",
inRegion: findISORegion(forAlpha2Code: "US")
)?
.absoluteString
}
== "https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App"
expect(
consequencesOf(
ITunesSearchAppStoreSearcher()
.searchURL(
for: "My App",
inRegion: findISORegion(forAlpha2Code: "US")
)?
.absoluteString
)
)
== (
// swiftlint:disable:next line_length
"https://itunes.apple.com/search?media=software&entity=desktopSoftware&country=US&term=My%20App",
nil,
"",
""
)
}
}
describe("store") {
@ -46,10 +61,11 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
let networkSession = MockFromFileNetworkSession(responseFile: "search/slack.json")
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
expect {
try searcher.search(for: "slack").wait()
}
.to(haveCount(39))
let consequences = consequencesOf(try searcher.search(for: "slack").wait())
expect(consequences.value).to(haveCount(39))
expect(consequences.error) == nil
expect(consequences.stdout).to(beEmpty())
expect(consequences.stderr).to(beEmpty())
}
}
@ -59,13 +75,12 @@ public final class ITunesSearchAppStoreSearcherSpec: QuickSpec {
let networkSession = MockFromFileNetworkSession(responseFile: "lookup/slack.json")
let searcher = ITunesSearchAppStoreSearcher(networkManager: NetworkManager(session: networkSession))
var result: SearchResult?
expect {
result = try searcher.lookup(appID: appID).wait()
}
.toNot(throwError())
let consequences = consequencesOf(try searcher.lookup(appID: appID).wait())
expect(consequences.error) == nil
expect(consequences.stdout).to(beEmpty())
expect(consequences.stderr).to(beEmpty())
guard let result else {
guard let result = consequences.value else {
fatalError("lookup result was nil")
}

View file

@ -21,7 +21,7 @@ public final class AppListFormatterSpec: QuickSpec {
}
describe("app list formatter") {
it("formats nothing as empty string") {
expect(format([])).to(beEmpty())
expect(consequencesOf(format([]))) == ("", nil, "", "")
}
it("can format a single product") {
let product = MockSoftwareProduct(
@ -31,30 +31,32 @@ public final class AppListFormatterSpec: QuickSpec {
bundleVersion: "19.2.1",
itemIdentifier: 12345
)
expect(format([product])) == "12345 Awesome App (19.2.1)"
expect(consequencesOf(format([product]))) == ("12345 Awesome App (19.2.1)", nil, "", "")
}
it("can format two products") {
expect(
format(
[
MockSoftwareProduct(
appName: "Awesome App",
bundleIdentifier: "",
bundlePath: "",
bundleVersion: "19.2.1",
itemIdentifier: 12345
),
MockSoftwareProduct(
appName: "Even Better App",
bundleIdentifier: "",
bundlePath: "",
bundleVersion: "1.2.0",
itemIdentifier: 67890
),
]
consequencesOf(
format(
[
MockSoftwareProduct(
appName: "Awesome App",
bundleIdentifier: "",
bundlePath: "",
bundleVersion: "19.2.1",
itemIdentifier: 12345
),
MockSoftwareProduct(
appName: "Even Better App",
bundleIdentifier: "",
bundlePath: "",
bundleVersion: "1.2.0",
itemIdentifier: 67890
),
]
)
)
)
== "12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)"
== ("12345 Awesome App (19.2.1)\n67890 Even Better App (1.2.0)", nil, "", "")
}
}
}

View file

@ -21,7 +21,7 @@ public final class SearchResultFormatterSpec: QuickSpec {
}
describe("search results formatter") {
it("formats nothing as empty string") {
expect(format([], false)).to(beEmpty())
expect(consequencesOf(format([], false))) == ("", nil, "", "")
}
it("can format a single result") {
let result = SearchResult(
@ -30,7 +30,7 @@ public final class SearchResultFormatterSpec: QuickSpec {
trackName: "Awesome App",
version: "19.2.1"
)
expect(format([result], false)) == " 12345 Awesome App (19.2.1)"
expect(consequencesOf(format([result], false))) == (" 12345 Awesome App (19.2.1)", nil, "", "")
}
it("can format a single result with price") {
let result = SearchResult(
@ -39,51 +39,61 @@ public final class SearchResultFormatterSpec: QuickSpec {
trackName: "Awesome App",
version: "19.2.1"
)
expect(format([result], true)) == " 12345 Awesome App (19.2.1) $9.87"
expect(consequencesOf(format([result], true)))
== (" 12345 Awesome App (19.2.1) $9.87", nil, "", "")
}
it("can format a two results") {
expect(
format(
[
SearchResult(
formattedPrice: "$9.87",
trackId: 12345,
trackName: "Awesome App",
version: "19.2.1"
),
SearchResult(
formattedPrice: "$0.01",
trackId: 67890,
trackName: "Even Better App",
version: "1.2.0"
),
],
false
consequencesOf(
format(
[
SearchResult(
formattedPrice: "$9.87",
trackId: 12345,
trackName: "Awesome App",
version: "19.2.1"
),
SearchResult(
formattedPrice: "$0.01",
trackId: 67890,
trackName: "Even Better App",
version: "1.2.0"
),
],
false
)
)
)
== " 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)"
== (" 12345 Awesome App (19.2.1)\n 67890 Even Better App (1.2.0)", nil, "", "")
}
it("can format a two results with prices") {
expect(
format(
[
SearchResult(
formattedPrice: "$9.87",
trackId: 12345,
trackName: "Awesome App",
version: "19.2.1"
),
SearchResult(
formattedPrice: "$0.01",
trackId: 67890,
trackName: "Even Better App",
version: "1.2.0"
),
],
true
consequencesOf(
format(
[
SearchResult(
formattedPrice: "$9.87",
trackId: 12345,
trackName: "Awesome App",
version: "19.2.1"
),
SearchResult(
formattedPrice: "$0.01",
trackId: 67890,
trackName: "Even Better App",
version: "1.2.0"
),
],
true
)
)
)
== " 12345 Awesome App (19.2.1) $9.87\n 67890 Even Better App (1.2.0) $0.01"
== (
" 12345 Awesome App (19.2.1) $9.87\n 67890 Even Better App (1.2.0) $0.01",
nil,
"",
""
)
}
}
}

View file

@ -20,15 +20,23 @@ public final class SearchResultListSpec: QuickSpec {
describe("search result list") {
it("can parse bbedit") {
expect(
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/bbedit.json")).resultCount
consequencesOf(
try JSONDecoder()
.decode(SearchResultList.self, from: Data(from: "search/bbedit.json"))
.resultCount
)
)
== 1
== (1, nil, "", "")
}
it("can parse things") {
expect(
try JSONDecoder().decode(SearchResultList.self, from: Data(from: "search/things.json")).resultCount
consequencesOf(
try JSONDecoder()
.decode(SearchResultList.self, from: Data(from: "search/things.json"))
.resultCount
)
)
== 50
== (50, nil, "", "")
}
}
}

View file

@ -20,11 +20,13 @@ public final class SearchResultSpec: QuickSpec {
describe("search result") {
it("can parse things") {
expect(
try JSONDecoder()
.decode(SearchResult.self, from: Data(from: "search/things-that-go-bump.json"))
.trackId
consequencesOf(
try JSONDecoder()
.decode(SearchResult.self, from: Data(from: "search/things-that-go-bump.json"))
.trackId
)
)
== 1_472_954_003
== (1_472_954_003, nil, "", "")
}
}
}

View file

@ -27,16 +27,28 @@ public final class SoftwareProductSpec: QuickSpec {
}
describe("software product") {
it("is not outdated when there is no new version available") {
expect(app.isOutdated(comparedTo: SearchResult(version: "1.0.0"))) == false
expect(consequencesOf(app.isOutdated(comparedTo: SearchResult(version: "1.0.0"))))
== (false, nil, "", "")
}
it("is outdated when there is a new version available") {
expect(app.isOutdated(comparedTo: SearchResult(version: "2.0.0"))) == true
expect(consequencesOf(app.isOutdated(comparedTo: SearchResult(version: "2.0.0"))))
== (true, nil, "", "")
}
it("is not outdated when the new version of mac-software requires a higher OS version") {
expect(app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))) == false
expect(
consequencesOf(
app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))
)
)
== (false, nil, "", "")
}
it("is not outdated when the new version of software requires a higher OS version") {
expect(app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))) == false
expect(
consequencesOf(
app.isOutdated(comparedTo: SearchResult(minimumOsVersion: "99.0.0", version: "3.0.0"))
)
)
== (false, nil, "", "")
}
}
}

View file

@ -8,29 +8,86 @@
import Foundation
func captureStream(
_ stream: UnsafeMutablePointer<FILE>,
encoding: String.Encoding = .utf8,
_ block: @escaping () throws -> Void
) rethrows -> String {
let originalFd = fileno(stream)
let duplicateFd = dup(originalFd)
@testable import mas
func consequencesOf(
streamEncoding: String.Encoding = .utf8,
_ expression: @autoclosure @escaping () throws -> Void
) -> (error: MASError?, stdout: String, stderr: String) {
let consequences = consequences(streamEncoding: streamEncoding, expression)
return (consequences.error, consequences.stdout, consequences.stderr)
}
func consequencesOf<T>(
streamEncoding: String.Encoding = .utf8,
_ expression: @autoclosure @escaping () throws -> T
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
consequences(streamEncoding: streamEncoding, expression)
}
// periphery:ignore
func consequencesOf(
streamEncoding: String.Encoding = .utf8,
_ body: @escaping () throws -> Void
) -> (error: MASError?, stdout: String, stderr: String) {
let consequences = consequences(streamEncoding: streamEncoding, body)
return (consequences.error, consequences.stdout, consequences.stderr)
}
// periphery:ignore
func consequencesOf<T>(
streamEncoding: String.Encoding = .utf8,
_ body: @escaping () throws -> T
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
consequences(streamEncoding: streamEncoding, body)
}
private func consequences<T>(
streamEncoding: String.Encoding = .utf8,
_ body: @escaping () throws -> T
) -> (value: T?, error: MASError?, stdout: String, stderr: String) {
let outOriginalFD = fileno(stdout)
let errOriginalFD = fileno(stderr)
let outDuplicateFD = dup(outOriginalFD)
defer {
close(duplicateFd)
close(outDuplicateFD)
}
let pipe = Pipe()
dup2(pipe.fileHandleForWriting.fileDescriptor, originalFd)
let errDuplicateFD = dup(errOriginalFD)
defer {
close(errDuplicateFD)
}
let outPipe = Pipe()
let errPipe = Pipe()
dup2(outPipe.fileHandleForWriting.fileDescriptor, outOriginalFD)
dup2(errPipe.fileHandleForWriting.fileDescriptor, errOriginalFD)
var value: T?
var thrownError: MASError?
do {
defer {
fflush(stream)
dup2(duplicateFd, originalFd)
pipe.fileHandleForWriting.closeFile()
fflush(stdout)
fflush(stderr)
dup2(outDuplicateFD, outOriginalFD)
dup2(errDuplicateFD, errOriginalFD)
outPipe.fileHandleForWriting.closeFile()
errPipe.fileHandleForWriting.closeFile()
}
try block()
value = try body()
} catch let error as MASError {
thrownError = error
} catch {
thrownError = MASError.failed(error: error as NSError)
}
return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: encoding) ?? ""
return (
value,
thrownError,
String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? "",
String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: streamEncoding) ?? ""
)
}