Skip to content

Commit 95397a9

Browse files
authored
Tests: Exercise output and diagnostic messages from command plugins (#7254)
This test exercises command plugin console output under different verbosity settings: ### Motivation: There doesn't seem to be a test which checks the handling of diagnostic messages from command plugins in a once place. This test will help to avoid regressions in this area. ### Modifications: * A new stub plugin prints to standard output or sends diagnostic output, depending on its arguments * A new test uses the stub to generate outputs and checks what `swift package` prints to the console at different verbosity levels ### Result: This test verifies existing functionality. No change in Swift Package Manager.
1 parent 4b7ee3e commit 95397a9

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// swift-tools-version: 5.9
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "CommandPluginDiagnostics",
7+
targets: [
8+
.plugin(
9+
name: "diagnostics-stub",
10+
capability: .command(intent: .custom(
11+
verb: "print-diagnostics",
12+
description: "Writes diagnostic messages for testing"
13+
))
14+
),
15+
.executableTarget(
16+
name: "placeholder"
17+
),
18+
]
19+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
import PackagePlugin
3+
4+
@main
5+
struct diagnostics_stub: CommandPlugin {
6+
// This is a helper for testing plugin diagnostics. It sends different messages to SwiftPM depending on its arguments.
7+
func performCommand(context: PluginContext, arguments: [String]) async throws {
8+
// Anything a plugin writes to standard output appears on standard output.
9+
// Printing to stderr will also go to standard output because SwiftPM combines
10+
// stdout and stderr before launching the plugin.
11+
if arguments.contains("print") {
12+
print("command plugin: print")
13+
}
14+
15+
// Diagnostics are collected by SwiftPM and printed to standard error, depending on the current log verbosity level.
16+
if arguments.contains("remark") {
17+
Diagnostics.remark("command plugin: Diagnostics.remark") // prefixed with 'info:' when printed
18+
}
19+
20+
if arguments.contains("warning") {
21+
Diagnostics.warning("command plugin: Diagnostics.warning") // prefixed with 'warning:' when printed
22+
}
23+
24+
if arguments.contains("error") {
25+
Diagnostics.error("command plugin: Diagnostics.error") // prefixed with 'error:' when printed
26+
}
27+
}
28+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// The Swift Programming Language
2+
// https://docs.swift.org/swift-book
3+
4+
print("Hello, world from executable target!")

Tests/CommandsTests/PackageToolTests.swift

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1874,6 +1874,115 @@ final class PackageToolTests: CommandsTestCase {
18741874
}
18751875
}
18761876

1877+
// Test reporting of plugin diagnostic messages at different verbosity levels
1878+
func testCommandPluginDiagnostics() throws {
1879+
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
1880+
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")
1881+
1882+
// Match patterns for expected messages
1883+
let isEmpty = StringPattern.equal("")
1884+
let isOnlyPrint = StringPattern.equal("command plugin: print\n")
1885+
let containsRemark = StringPattern.contains("command plugin: Diagnostics.remark")
1886+
let containsWarning = StringPattern.contains("command plugin: Diagnostics.warning")
1887+
let containsError = StringPattern.contains("command plugin: Diagnostics.error")
1888+
1889+
try fixture(name: "Miscellaneous/Plugins/CommandPluginDiagnosticsStub") { fixturePath in
1890+
func runPlugin(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws {
1891+
let (stdout, stderr) = try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath)
1892+
completion(stdout, stderr)
1893+
}
1894+
1895+
// Diagnostics.error causes SwiftPM to return a non-zero exit code, but we still need to check stdout and stderr
1896+
func runPluginWithError(flags: [String], diagnostics: [String], completion: (String, String) -> Void) throws {
1897+
XCTAssertThrowsError(try SwiftPM.Package.execute(flags + ["print-diagnostics"] + diagnostics, packagePath: fixturePath)) { error in
1898+
guard case SwiftPMError.executionFailure(_, let stdout, let stderr) = error else {
1899+
return XCTFail("invalid error \(error)")
1900+
}
1901+
completion(stdout, stderr)
1902+
}
1903+
}
1904+
1905+
// Default verbosity
1906+
// - stdout is always printed
1907+
// - Diagnostics below 'warning' are suppressed
1908+
1909+
try runPlugin(flags: [], diagnostics: ["print"]) { stdout, stderr in
1910+
XCTAssertMatch(stdout, isOnlyPrint)
1911+
XCTAssertMatch(stderr, isEmpty)
1912+
}
1913+
1914+
try runPlugin(flags: [], diagnostics: ["print", "remark"]) { stdout, stderr in
1915+
XCTAssertMatch(stdout, isOnlyPrint)
1916+
XCTAssertMatch(stderr, isEmpty)
1917+
}
1918+
1919+
try runPlugin(flags: [], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1920+
XCTAssertMatch(stdout, isOnlyPrint)
1921+
XCTAssertMatch(stderr, containsWarning)
1922+
}
1923+
1924+
try runPluginWithError(flags: [], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1925+
XCTAssertMatch(stdout, isOnlyPrint)
1926+
XCTAssertMatch(stderr, containsWarning)
1927+
XCTAssertMatch(stderr, containsError)
1928+
}
1929+
1930+
// Quiet Mode
1931+
// - stdout is always printed
1932+
// - Diagnostics below 'error' are suppressed
1933+
1934+
try runPlugin(flags: ["-q"], diagnostics: ["print"]) { stdout, stderr in
1935+
XCTAssertMatch(stdout, isOnlyPrint)
1936+
XCTAssertMatch(stderr, isEmpty)
1937+
}
1938+
1939+
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark"]) { stdout, stderr in
1940+
XCTAssertMatch(stdout, isOnlyPrint)
1941+
XCTAssertMatch(stderr, isEmpty)
1942+
}
1943+
1944+
try runPlugin(flags: ["-q"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1945+
XCTAssertMatch(stdout, isOnlyPrint)
1946+
XCTAssertMatch(stderr, isEmpty)
1947+
}
1948+
1949+
try runPluginWithError(flags: ["-q"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1950+
XCTAssertMatch(stdout, isOnlyPrint)
1951+
XCTAssertNoMatch(stderr, containsRemark)
1952+
XCTAssertNoMatch(stderr, containsWarning)
1953+
XCTAssertMatch(stderr, containsError)
1954+
}
1955+
1956+
// Verbose Mode
1957+
// - stdout is always printed
1958+
// - All diagnostics are printed
1959+
// - Substantial amounts of additional compiler output are also printed
1960+
1961+
try runPlugin(flags: ["-v"], diagnostics: ["print"]) { stdout, stderr in
1962+
XCTAssertMatch(stdout, isOnlyPrint)
1963+
// At this level stderr contains extra compiler output even if the plugin does not print diagnostics
1964+
}
1965+
1966+
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark"]) { stdout, stderr in
1967+
XCTAssertMatch(stdout, isOnlyPrint)
1968+
XCTAssertMatch(stderr, containsRemark)
1969+
}
1970+
1971+
try runPlugin(flags: ["-v"], diagnostics: ["print", "remark", "warning"]) { stdout, stderr in
1972+
XCTAssertMatch(stdout, isOnlyPrint)
1973+
XCTAssertMatch(stderr, containsRemark)
1974+
XCTAssertMatch(stderr, containsWarning)
1975+
}
1976+
1977+
try runPluginWithError(flags: ["-v"], diagnostics: ["print", "remark", "warning", "error"]) { stdout, stderr in
1978+
XCTAssertMatch(stdout, isOnlyPrint)
1979+
XCTAssertMatch(stderr, containsRemark)
1980+
XCTAssertMatch(stderr, containsWarning)
1981+
XCTAssertMatch(stderr, containsError)
1982+
}
1983+
}
1984+
}
1985+
18771986
func testCommandPluginNetworkingPermissions(permissionsManifestFragment: String, permissionError: String, reason: String, remedy: [String]) throws {
18781987
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
18791988
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")

0 commit comments

Comments
 (0)