mirror of
https://github.com/mas-cli/mas
synced 2025-02-16 12:38:30 +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
|
/// Terminal Control Sequence Indicator
|
||||||
let csi = "\u{001B}["
|
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
|
private var standardError = FileHandle.standardError
|
||||||
|
|
||||||
extension FileHandle: TextOutputStream {
|
extension FileHandle: TextOutputStream {
|
||||||
|
@ -121,3 +64,30 @@ func clearLine() {
|
||||||
print("\(csi)2K\(csi)0G", terminator: "")
|
print("\(csi)2K\(csi)0G", terminator: "")
|
||||||
fflush(stdout)
|
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.
|
// Copyright © 2018 mas-cli. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Nimble
|
import Nimble
|
||||||
import Quick
|
import Quick
|
||||||
|
|
||||||
|
@ -47,12 +48,12 @@ public class InfoSpec: QuickSpec {
|
||||||
version: "1.0"
|
version: "1.0"
|
||||||
)
|
)
|
||||||
storeSearch.apps[mockResult.trackId] = mockResult
|
storeSearch.apps[mockResult.trackId] = mockResult
|
||||||
let output = OutputListener()
|
|
||||||
expect {
|
expect {
|
||||||
|
try captureStream(stdout) {
|
||||||
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
|
try Mas.Info.parse([String(mockResult.trackId)]).run(storeSearch: storeSearch)
|
||||||
}
|
}
|
||||||
.toNot(throwError())
|
}
|
||||||
expect(output.contents) == """
|
== """
|
||||||
Awesome App 1.0 [2.0]
|
Awesome App 1.0 [2.0]
|
||||||
By: Awesome Dev
|
By: Awesome Dev
|
||||||
Released: 2019-01-07
|
Released: 2019-01-07
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// Copyright © 2018 mas-cli. All rights reserved.
|
// Copyright © 2018 mas-cli. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Nimble
|
import Nimble
|
||||||
import Quick
|
import Quick
|
||||||
|
|
||||||
|
@ -19,9 +20,11 @@ public class ListSpec: QuickSpec {
|
||||||
describe("list command") {
|
describe("list command") {
|
||||||
it("lists apps") {
|
it("lists apps") {
|
||||||
expect {
|
expect {
|
||||||
|
try captureStream(stderr) {
|
||||||
try Mas.List.parse([]).run(appLibrary: AppLibraryMock())
|
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.
|
// Copyright © 2018 mas-cli. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Nimble
|
import Nimble
|
||||||
import Quick
|
import Quick
|
||||||
|
|
||||||
|
@ -18,10 +19,39 @@ public class OutdatedSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
describe("outdated command") {
|
describe("outdated command") {
|
||||||
it("displays apps with pending updates") {
|
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 {
|
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") {
|
it("removes an app") {
|
||||||
mockLibrary.installedApps.append(app)
|
mockLibrary.installedApps.append(app)
|
||||||
expect {
|
expect {
|
||||||
|
try captureStream(stdout) {
|
||||||
try uninstall.run(appLibrary: mockLibrary)
|
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") {
|
it("fails if there is a problem with the trash command") {
|
||||||
var brokenUninstall = app // make mutable copy
|
var brokenUninstall = app // make mutable copy
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
// Copyright © 2018 mas-cli. All rights reserved.
|
// Copyright © 2018 mas-cli. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Nimble
|
import Nimble
|
||||||
import Quick
|
import Quick
|
||||||
|
|
||||||
|
@ -17,11 +18,14 @@ public class UpgradeSpec: QuickSpec {
|
||||||
Mas.initialize()
|
Mas.initialize()
|
||||||
}
|
}
|
||||||
describe("upgrade command") {
|
describe("upgrade command") {
|
||||||
it("upgrades stuff") {
|
it("finds no upgrades") {
|
||||||
expect {
|
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.
|
// Copyright © 2018 mas-cli. All rights reserved.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Nimble
|
import Nimble
|
||||||
import Quick
|
import Quick
|
||||||
|
|
||||||
|
@ -19,9 +20,11 @@ public class VersionSpec: QuickSpec {
|
||||||
describe("version command") {
|
describe("version command") {
|
||||||
it("displays the current version") {
|
it("displays the current version") {
|
||||||
expect {
|
expect {
|
||||||
|
try captureStream(stdout) {
|
||||||
try Mas.Version.parse([]).run()
|
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…
Add table
Reference in a new issue