Skip to content

Migrate PluginTests to Swift Testing #8793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

cmcgee1024
Copy link
Member

Migrate the test suite to Swift Testing.

Explicitly mark tests that are enabled/disabled.

Add bug links to issues and radars for better tracking combined
withKnownIssues so that when bugs are fixed there is immediate
notification that the tests can be enabled.

@cmcgee1024
Copy link
Member Author

@swift-ci please test

Copy link
Contributor

@bkhouri bkhouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are still references to custom XCTAssertion calls, which mixes XCTest and Swift Testing. Not to mention I believe the use of #file and #line should be replaced with #_sourceLocation based on the some Swift Testing trait documentation: https://developer.apple.com/documentation/testing/trait/enabled(if:_:sourcelocation:).

try await withKnownIssue {
// Try again with the Swift Build build system
try await fixture(name: "Miscellaneous/Plugins") { fixturePath in
let (stdout, _) = try await executeSwiftBuild(fixturePath.appending("MySourceGenPlugin"), configuration: .Debug, extraArgs: ["--vv", "--product", "MyLocalTool", "--build-system", "swiftbuild"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: instead of creating a separate code block in. the same "test case", consider using parameterized tests.

See #8714 as an example.

This comment applies to all applicable tests in suite.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm aware the parameterized tests don't permit skipping certain parameter combinations, or have separate parts that have different known issue when conditions. I expect there to be too many parameters to make the parameterization worthwhile.

Copy link
Contributor

@bkhouri bkhouri Jun 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The withKnownIssue can accept a when: argument.

e.g.:

withKnownIssue {
   // test code
} when: {
 buildSystem == .swiftbuild
}

See the known issues documentation.

Regarding skipping: It is my opinion that a test should be skipped if, and only if, the test is not applicable. Otherwise, we ought to use withKnownIssue to report the currently failing behaviour.

func testInternalExecutableAvailableOnlyToPlugin() async throws {
// Only run the test if the environment in which we're running actually supports Swift concurrency (which the plugin APIs require).
try XCTSkipIf(!UserToolchain.default.supportsSwiftConcurrency(), "skipping because test environment doesn't support concurrency")

try await fixture(name: "Miscellaneous/Plugins") { fixturePath in
await XCTAssertThrowsCommandExecutionError(try await executeSwiftBuild(fixturePath.appending("InvalidUseOfInternalPluginExecutable")), "Illegally used internal executable") { error in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: (blocking): This test has a mixture of XCTest and Swift Testing. We need convert this to Swift Testing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, do you know if anyone has make equivalents for Swift Testing for these utilities in _InternalTestSupport? There's significant logic in these utility methods, and not entirely obvious how to convert them to the new testing API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a look at BuildCommandTests.swift here for a possible conversion option:

https://github.com/swiftlang/swift-package-manager/pull/8714/files#diff-b1845cad8f64e37500b06818ff71629726c14cf5a8d34add6ef3a9d38236cfe5

In addition, has a look a the Record issues in the Migrate a test from XCTest.

}
}
catch {
XCTFail("error \(String(describing: error))", file: file, line: line)
try #require(true == false, "error \(String(describing: error)) at \(file) and \(line)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: We should use `Issue.record("error (String(describing: error)) at (sourceLocation)")" here instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I couldn't find the API to raise an issue directly.

@@ -945,15 +976,16 @@ final class PluginTests: XCTestCase {
delegateQueue.sync {
pid = delegate.parsedProcessIdentifier
}
guard let pid else {
throw XCTSkip("skipping test because no pid was received from the plugin; being investigated as rdar://88792829\n\(delegate.diagnostics.description)")
guard let pid = pid else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (possibly-blocking): if we want to fail the test, we should invoke try #require(pid, "Test failed because no pid was received from the plugin; being investigated as rdar://88792829\n\(delegate.diagnostics.description)")

Can we add .bug("rdar://88792829") trait to the test, and preferably reference a GitHub issue.

Copy link
Member Author

@cmcgee1024 cmcgee1024 Jun 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code that's written here before appears to want to skip the test in middle in this case, not fail it. I'm not sure how to do that with Swift Test API, other than to just return. How would one print this information out without just printing it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@grynspan or @stmontgomery might have ideas on how to handle this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using withKnownIssue and use try #require in its body closure to unwrap the pid and use it in the subsequent code. You could use a matching: closure on withKnownIssue to only match the specific expectation which is attempting to unwrap pid.

@bkhouri
Copy link
Contributor

bkhouri commented Jun 10, 2025

@swift-ci test windows

@cmcgee1024
Copy link
Member Author

@swift-ci please test

@cmcgee1024
Copy link
Member Author

@swift-ci please test

@cmcgee1024
Copy link
Member Author

@swift-ci test Windows

@cmcgee1024 cmcgee1024 enabled auto-merge (squash) June 12, 2025 19:02
Copy link
Contributor

@bkhouri bkhouri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking, however there are changes that should be addressed. Addressing them in a follow-up PR is acceptable, unless the branch will be updated.

XCTAssert(stdout.contains("Linking MyOtherLocalTool"), "stdout:\n\(stdout)")
XCTAssert(stdout.contains("Build of product 'MyOtherLocalTool' complete!"), "stdout:\n\(stdout)")
}
try await withKnownIssue {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (non-blocking, if-updating-pr): Can we wrap the withKnownIssue around each build system? This should give more fine grained indication if this test will be supported on windows for native and SwiftBuild build system.

This comment applies to all applicable test cases.

}

try await fixture(name: "Miscellaneous/Plugins") { fixturePath in
await XCTAssertThrowsCommandExecutionError(try await executeSwiftBuild(fixturePath.appending("InvalidUseOfInternalPluginExecutable")), "Illegally used internal executable"
) { error in
let error = try await #require(throws: SwiftPMError.self, "Illegally used internal executable") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Are we able to add an expectation to verify we failed in the expected way?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just attempting to faithfully duplicate the logic from the XCTest function here. It's expecting that the SwiftPMError is thrown, and that the error contains certain details taken from the XCTAssertThrowsCommandExecutionError.

await XCTAssertThrowsCommandExecutionError(try await executeSwiftBuild(fixturePath.appending("InvalidUseOfInternalPluginExecutable")), "Illegally used internal executable"
) { error in
let error = try await #require(throws: SwiftPMError.self, "Illegally used internal executable") {
try await executeSwiftBuild(fixturePath.appending("InvalidUseOfInternalPluginExecutable"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (possibly-blocking): This running the same action as lie 162. Did we intend to build this using SwiftBuild build system? If so, can we update this test? It's ok to do this in a follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test already had these two cases, one that checks stderr's contents. It didn't cover swiftbuild build system. This is just faithfully converting it to Swift Testing as-is.

try await withKnownIssue {
// Try again with the Swift Build build system
try await fixture(name: "Miscellaneous/Plugins") { fixturePath in
let (stdout, _) = try await executeSwiftBuild(fixturePath.appending("MyBuildToolPluginDependencies"), extraArgs: ["--vv", "--build-system", "swiftbuild"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: does this test now need the --vv argument?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's just that without the extra verbose logging, when the test does fail it doesn't yield very useful information for things like linking failures.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the verbose logging so that it doesn't fill up the logs. With linker errors, one will need a reproducing environment to be able to exfiltrate the linker arguments.

@@ -643,62 +698,63 @@ final class PluginTests: XCTestCase {
)
}
if expectFailure {
XCTAssertFalse(success, "expected command to fail, but it succeeded", file: file, line: line)
#expect(!success, "expected command to fail, but it succeeded")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: should we pass through the sourceLocation argument to the expectation so the failure has a more meaningful file and line number?

}
else {
XCTAssertTrue(success, "expected command to succeed, but it failed", file: file, line: line)
#expect(success, "expected command to succeed, but it failed")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: should we pass through the sourceLocation argument to the expectation so the failure has a more meaningful file and line number?

}
}
catch {
XCTFail("error \(String(describing: error))", file: file, line: line)
Issue.record("error \(String(describing: error)) at \(sourceLocation)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): Instead of include the source location in failure message, set the sourceLocation argument.

Suggested change
Issue.record("error \(String(describing: error)) at \(sourceLocation)")
Issue.record("error \(String(describing: error))", sourceLocation: sourceLocation)`

See https://developer.apple.com/documentation/testing/issue/record(_:_:sourcelocation:)

@@ -925,15 +979,15 @@ final class PluginTests: XCTestCase {
do {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): this do block could be replace with

#expect(throws: Never.self) {
    try scriptRunner.cancel(deadline: .now() + .seconds(5))
}

let (stdout, stderr) = try await executeSwiftBuild(
fixturePath.appending(component: "MySourceGenPlugin"),
configuration: .Debug,
extraArgs: ["--vv", "--product", "MyLocalTool", "-Xbuild-tools-swiftc", "-DUSE_CREATE", "--build-system", "swiftbuild"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: is --vv required to to pass this test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "--vv" will give us enough information from swift build for problem diagnosis for things like linker failures. Otherwise, it's just a single error message indicating that linking failed, which isn't very helpful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the verbose logging so that it doesn't fill up the logs. With linker errors, one will need a reproducing environment to be able to exfiltrate the linker arguments.

if diagnostics.isEmpty { return }

let description = diagnostics.map { "- " + $0.description }.joined(separator: "\n")
Issue.record("Found unexpected diagnostics: \n\(description) \(sourceLocation.fileName):\(sourceLocation.line)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (if-patch-updated): instead of putting the source location in the error message, can we call Issue.record with the sourceLocation argument set. That's, Swift Testing will accurately report the filename and line number.

Suggested change
Issue.record("Found unexpected diagnostics: \n\(description) \(sourceLocation.fileName):\(sourceLocation.line)")
Issue.record("Found unexpected diagnostics: \n\(description)", sourceLocation: sourceLocation)

See https://developer.apple.com/documentation/testing/issue/record(_:sourcelocation:)

@cmcgee1024 cmcgee1024 disabled auto-merge June 12, 2025 19:09
@cmcgee1024 cmcgee1024 enabled auto-merge (squash) June 13, 2025 15:57
@cmcgee1024
Copy link
Member Author

@swift-ci please test

@cmcgee1024
Copy link
Member Author

@swift-ci test Windows

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants