mirror of
https://github.com/mas-cli/mas
synced 2024-11-22 03:23:08 +00:00
Add captureStream(…)
to observe stdout & stderr in tests.
Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com>
This commit is contained in:
parent
dccac33abb
commit
265326dede
10 changed files with 85 additions and 153 deletions
|
@ -13,63 +13,6 @@ import Foundation
|
|||
/// Terminal Control Sequence Indicator
|
||||
let csi = "\u{001B}["
|
||||
|
||||
#if DEBUG
|
||||
|
||||
var printObserver: ((String) -> Void)?
|
||||
|
||||
// Override global print for testability.
|
||||
// See masTests/OutputListener.swift.
|
||||
func print(
|
||||
_ items: Any...,
|
||||
separator: String = " ",
|
||||
terminator: String = "\n"
|
||||
) {
|
||||
if let observer = printObserver {
|
||||
let output =
|
||||
items
|
||||
.map { "\($0)" }
|
||||
.joined(separator: separator)
|
||||
.appending(terminator)
|
||||
observer(output)
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
for item in items {
|
||||
Swift.print(prefix, terminator: "")
|
||||
Swift.print(item, terminator: "")
|
||||
prefix = separator
|
||||
}
|
||||
|
||||
Swift.print(terminator, terminator: "")
|
||||
}
|
||||
|
||||
func print(
|
||||
_ items: Any...,
|
||||
separator: String = " ",
|
||||
terminator: String = "\n",
|
||||
to output: inout some TextOutputStream
|
||||
) {
|
||||
if let observer = printObserver {
|
||||
let output =
|
||||
items
|
||||
.map { "\($0)" }
|
||||
.joined(separator: separator)
|
||||
.appending(terminator)
|
||||
observer(output)
|
||||
}
|
||||
|
||||
var prefix = ""
|
||||
for item in items {
|
||||
Swift.print(prefix, terminator: "", to: &output)
|
||||
Swift.print(item, terminator: "", to: &output)
|
||||
prefix = separator
|
||||
}
|
||||
|
||||
Swift.print(terminator, terminator: "", to: &output)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
private var standardError = FileHandle.standardError
|
||||
|
||||
extension FileHandle: TextOutputStream {
|
||||
|
@ -121,3 +64,30 @@ func clearLine() {
|
|||
print("\(csi)2K\(csi)0G", terminator: "")
|
||||
fflush(stdout)
|
||||
}
|
||||
|
||||
func captureStream(
|
||||
_ stream: UnsafeMutablePointer<FILE>,
|
||||
encoding: String.Encoding = .utf8,
|
||||
_ block: @escaping () throws -> Void
|
||||
) throws -> String {
|
||||
let originalFd = fileno(stream)
|
||||
let duplicateFd = dup(originalFd)
|
||||
defer {
|
||||
close(duplicateFd)
|
||||
}
|
||||
|
||||
let pipe = Pipe()
|
||||
dup2(pipe.fileHandleForWriting.fileDescriptor, originalFd)
|
||||
|
||||
do {
|
||||
defer {
|
||||
fflush(stream)
|
||||
dup2(duplicateFd, originalFd)
|
||||
pipe.fileHandleForWriting.closeFile()
|
||||
}
|
||||
|
||||
try block()
|
||||
}
|
||||
|
||||
return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: encoding) ?? ""
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -47,12 +48,12 @@ public class InfoSpec: QuickSpec {
|
|||
version: "1.0"
|
||||
)
|
||||
storeSearch.apps[mockResult.trackId] = mockResult
|
||||
let output = OutputListener()
|
||||
expect {
|
||||
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
|
||||
try captureStream(stdout) {
|
||||
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
expect(output.contents) == """
|
||||
== """
|
||||
Awesome App 1.0 [2.0]
|
||||
By: Awesome Dev
|
||||
Released: 2019-01-07
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,9 +20,11 @@ public class ListSpec: QuickSpec {
|
|||
describe("list command") {
|
||||
it("lists apps") {
|
||||
expect {
|
||||
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
|
||||
try captureStream(stderr) {
|
||||
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "Error: No installed apps found\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -18,10 +19,39 @@ public class OutdatedSpec: QuickSpec {
|
|||
}
|
||||
describe("outdated command") {
|
||||
it("displays apps with pending updates") {
|
||||
let mockSearchResult =
|
||||
SearchResult(
|
||||
bundleId: "au.id.haroldchu.mac.Bandwidth",
|
||||
currentVersionReleaseDate: "2024-09-02T00:27:00Z",
|
||||
fileSizeBytes: "998130",
|
||||
minimumOsVersion: "10.13",
|
||||
price: 0,
|
||||
sellerName: "Harold Chu",
|
||||
sellerUrl: "https://example.com",
|
||||
trackId: 490_461_369,
|
||||
trackName: "Bandwidth+",
|
||||
trackViewUrl: "https://apps.apple.com/us/app/bandwidth/id490461369?mt=12&uo=4",
|
||||
version: "1.28"
|
||||
)
|
||||
let mockStoreSearch = StoreSearchMock()
|
||||
mockStoreSearch.apps[mockSearchResult.trackId] = mockSearchResult
|
||||
|
||||
let mockAppLibrary = AppLibraryMock()
|
||||
mockAppLibrary.installedApps.append(
|
||||
SoftwareProductMock(
|
||||
appName: mockSearchResult.trackName,
|
||||
bundleIdentifier: mockSearchResult.bundleId,
|
||||
bundlePath: "/Applications/Bandwidth+.app",
|
||||
bundleVersion: "1.27",
|
||||
itemIdentifier: NSNumber(value: mockSearchResult.trackId)
|
||||
)
|
||||
)
|
||||
expect {
|
||||
try Mas.Outdated.parse([]).run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
try captureStream(stdout) {
|
||||
try Mas.Outdated.parse([]).run(appLibrary: mockAppLibrary, storeSearch: mockStoreSearch)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "490461369 Bandwidth+ (1.27 -> 1.28)\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,9 +63,11 @@ public class UninstallSpec: QuickSpec {
|
|||
it("removes an app") {
|
||||
mockLibrary.installedApps.append(app)
|
||||
expect {
|
||||
try uninstall.run(appLibrary: mockLibrary)
|
||||
try captureStream(stdout) {
|
||||
try uninstall.run(appLibrary: mockLibrary)
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== " 1111 slack (0.0)\n==> Some App /tmp/Some.app\n==> (not removed, dry run)\n"
|
||||
}
|
||||
it("fails if there is a problem with the trash command") {
|
||||
var brokenUninstall = app // make mutable copy
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -17,11 +18,14 @@ public class UpgradeSpec: QuickSpec {
|
|||
Mas.initialize()
|
||||
}
|
||||
describe("upgrade command") {
|
||||
it("upgrades stuff") {
|
||||
it("finds no upgrades") {
|
||||
expect {
|
||||
try Mas.Upgrade.parse([]).run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
try captureStream(stderr) {
|
||||
try Mas.Upgrade.parse([])
|
||||
.run(appLibrary: AppLibraryMock(), storeSearch: StoreSearchMock())
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== "Warning: Nothing found to upgrade\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
// Copyright © 2018 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
|
@ -19,9 +20,11 @@ public class VersionSpec: QuickSpec {
|
|||
describe("version command") {
|
||||
it("displays the current version") {
|
||||
expect {
|
||||
try Mas.Version.parse([]).run()
|
||||
try captureStream(stdout) {
|
||||
try Mas.Version.parse([]).run()
|
||||
}
|
||||
}
|
||||
.toNot(throwError())
|
||||
== Package.version + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// OutputListener.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/7/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import mas
|
||||
|
||||
/// Test helper for monitoring strings written to stdout. Modified from:
|
||||
/// https://stackoverflow.com/a/53569018
|
||||
class OutputListener {
|
||||
/// Buffers strings written to stdout
|
||||
var contents = ""
|
||||
|
||||
init() {
|
||||
printObserver = { [weak self] text in
|
||||
strongify(self) { context in
|
||||
context.contents += text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
printObserver = nil
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// OutputListenerSpec.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/8/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import Nimble
|
||||
import Quick
|
||||
|
||||
@testable import mas
|
||||
|
||||
public class OutputListenerSpec: QuickSpec {
|
||||
override public static func spec() {
|
||||
beforeSuite {
|
||||
Mas.initialize()
|
||||
}
|
||||
describe("output listener") {
|
||||
it("can intercept a single line written stdout") {
|
||||
let output = OutputListener()
|
||||
|
||||
print("hi there", terminator: "")
|
||||
|
||||
expect(output.contents) == "hi there"
|
||||
}
|
||||
it("can intercept multiple lines written stdout") {
|
||||
let output = OutputListener()
|
||||
|
||||
print("hi there")
|
||||
|
||||
expect(output.contents) == """
|
||||
hi there
|
||||
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Strongify.swift
|
||||
// masTests
|
||||
//
|
||||
// Created by Ben Chatelain on 1/8/19.
|
||||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
// https://medium.com/@merowing_/stop-weak-strong-dance-in-swift-3aec6d3563d4
|
||||
|
||||
func strongify<Context: AnyObject>(_ context: Context?, closure: (Context) -> Void) {
|
||||
guard let strongContext = context else { return }
|
||||
closure(strongContext)
|
||||
}
|
Loading…
Reference in a new issue