From 265326dedec528fcb504846e7a26b0ddcba63145 Mon Sep 17 00:00:00 2001 From: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> Date: Wed, 2 Oct 2024 09:49:30 -0400 Subject: [PATCH] =?UTF-8?q?Add=20`captureStream(=E2=80=A6)`=20to=20observe?= =?UTF-8?q?=20stdout=20&=20stderr=20in=20tests.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Ross Goldberg <484615+rgoldberg@users.noreply.github.com> --- Sources/mas/Formatters/Utilities.swift | 84 +++++++-------------- Tests/masTests/Commands/InfoSpec.swift | 9 ++- Tests/masTests/Commands/ListSpec.swift | 7 +- Tests/masTests/Commands/OutdatedSpec.swift | 34 ++++++++- Tests/masTests/Commands/UninstallSpec.swift | 6 +- Tests/masTests/Commands/UpgradeSpec.swift | 10 ++- Tests/masTests/Commands/VersionSpec.swift | 7 +- Tests/masTests/OutputListener.swift | 28 ------- Tests/masTests/OutputListenerSpec.swift | 39 ---------- Tests/masTests/Strongify.swift | 14 ---- 10 files changed, 85 insertions(+), 153 deletions(-) delete mode 100644 Tests/masTests/OutputListener.swift delete mode 100644 Tests/masTests/OutputListenerSpec.swift delete mode 100644 Tests/masTests/Strongify.swift diff --git a/Sources/mas/Formatters/Utilities.swift b/Sources/mas/Formatters/Utilities.swift index f168e2c..c5c940e 100644 --- a/Sources/mas/Formatters/Utilities.swift +++ b/Sources/mas/Formatters/Utilities.swift @@ -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, + 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) ?? "" +} diff --git a/Tests/masTests/Commands/InfoSpec.swift b/Tests/masTests/Commands/InfoSpec.swift index 4e787ba..35ac88a 100644 --- a/Tests/masTests/Commands/InfoSpec.swift +++ b/Tests/masTests/Commands/InfoSpec.swift @@ -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 diff --git a/Tests/masTests/Commands/ListSpec.swift b/Tests/masTests/Commands/ListSpec.swift index 5cb373d..811ed7f 100644 --- a/Tests/masTests/Commands/ListSpec.swift +++ b/Tests/masTests/Commands/ListSpec.swift @@ -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" } } } diff --git a/Tests/masTests/Commands/OutdatedSpec.swift b/Tests/masTests/Commands/OutdatedSpec.swift index 71ef84b..f1890de 100644 --- a/Tests/masTests/Commands/OutdatedSpec.swift +++ b/Tests/masTests/Commands/OutdatedSpec.swift @@ -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" } } } diff --git a/Tests/masTests/Commands/UninstallSpec.swift b/Tests/masTests/Commands/UninstallSpec.swift index 072473e..d650044 100644 --- a/Tests/masTests/Commands/UninstallSpec.swift +++ b/Tests/masTests/Commands/UninstallSpec.swift @@ -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 diff --git a/Tests/masTests/Commands/UpgradeSpec.swift b/Tests/masTests/Commands/UpgradeSpec.swift index f22d450..e5a8406 100644 --- a/Tests/masTests/Commands/UpgradeSpec.swift +++ b/Tests/masTests/Commands/UpgradeSpec.swift @@ -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" } } } diff --git a/Tests/masTests/Commands/VersionSpec.swift b/Tests/masTests/Commands/VersionSpec.swift index 46c2baa..ec08193 100644 --- a/Tests/masTests/Commands/VersionSpec.swift +++ b/Tests/masTests/Commands/VersionSpec.swift @@ -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" } } } diff --git a/Tests/masTests/OutputListener.swift b/Tests/masTests/OutputListener.swift deleted file mode 100644 index 14298f6..0000000 --- a/Tests/masTests/OutputListener.swift +++ /dev/null @@ -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 - } -} diff --git a/Tests/masTests/OutputListenerSpec.swift b/Tests/masTests/OutputListenerSpec.swift deleted file mode 100644 index c5d02a6..0000000 --- a/Tests/masTests/OutputListenerSpec.swift +++ /dev/null @@ -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 - - """ - } - } - } -} diff --git a/Tests/masTests/Strongify.swift b/Tests/masTests/Strongify.swift deleted file mode 100644 index a87ce9a..0000000 --- a/Tests/masTests/Strongify.swift +++ /dev/null @@ -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: Context?, closure: (Context) -> Void) { - guard let strongContext = context else { return } - closure(strongContext) -}