Skip to content

Add descriptive error when attempting to list tests before building #8046

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

Merged
merged 4 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Sources/Basics/Errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,26 @@ public struct InternalError: Error {
}
}

/// Wraps another error and provides additional context when printed.
/// This is useful for user facing errors that need to provide a user friendly message
/// explaning why an error might have occured, while still showing the detailed underlying error.
public struct ErrorWithContext<E: Error>: Error {
public let error: E
public let context: String
public init(_ error: E, _ context: String) {
self.error = error
self.context = context
}
}

extension ErrorWithContext: LocalizedError {
public var errorDescription: String? {
return (context.split(separator: "\n") + [error.interpolationDescription])
.map { "\t\($0)" }
.joined(separator: "\n")
}
}

extension Error {
public var interpolationDescription: String {
switch self {
Expand Down
25 changes: 25 additions & 0 deletions Sources/Commands/SwiftTestCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import SPMBuildCore
import func TSCLibc.exit
import Workspace

import struct TSCBasic.FileSystemError
import struct TSCBasic.ByteString
import enum TSCBasic.JSON
import class Basics.AsyncProcess
Expand Down Expand Up @@ -725,6 +726,23 @@ extension SwiftTestCommand {
var _deprecated_passthrough: Bool = false

func run(_ swiftCommandState: SwiftCommandState) async throws {
do {
try await self.runCommand(swiftCommandState)
} catch let error as FileSystemError {
if sharedOptions.shouldSkipBuilding {
throw ErrorWithContext(error, """
Test build artifacts were not found in the build folder.
The `--skip-build` flag was provided; either build the tests first with \
`swift build --build tests` or rerun the `swift test list` command without \
`--skip-build`
"""
)
}
throw error
}
}

func runCommand(_ swiftCommandState: SwiftCommandState) async throws {
let (productsBuildParameters, toolsBuildParameters) = try swiftCommandState.buildParametersForTest(
enableCodeCoverage: false,
shouldSkipBuilding: sharedOptions.shouldSkipBuilding
Expand Down Expand Up @@ -781,6 +799,13 @@ extension SwiftTestCommand {
})
if result == .failure {
swiftCommandState.executionStatus = .failure
// If the runner reports failure do a check to ensure
// all the binaries are present on the file system.
for path in testProducts.map(\.binaryPath) {
if !swiftCommandState.fileSystem.exists(path) {
throw FileSystemError(.noEntry, path)
}
}
}
} else if let testEntryPointPath {
// Cannot run Swift Testing because an entry point file was used and the developer
Expand Down
34 changes: 32 additions & 2 deletions Sources/Commands/Utilities/TestingSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import PackageModel
import SPMBuildCore
import Workspace

import struct TSCBasic.FileSystemError
import class Basics.AsyncProcess
import var TSCBasic.stderrStream
import var TSCBasic.stdoutStream
Expand Down Expand Up @@ -123,8 +124,13 @@ enum TestingSupport {
sanitizers: sanitizers,
library: .xctest
)
try Self.runProcessWithExistenceCheck(
path: path,
fileSystem: swiftCommandState.fileSystem,
args: args,
env: env
)

try AsyncProcess.checkNonZeroExit(arguments: args, environment: env)
// Read the temporary file's content.
return try swiftCommandState.fileSystem.readFileContents(AbsolutePath(tempFile.path))
}
Expand All @@ -139,12 +145,36 @@ enum TestingSupport {
library: .xctest
)
args = [path.description, "--dump-tests-json"]
let data = try AsyncProcess.checkNonZeroExit(arguments: args, environment: env)
let data = try Self.runProcessWithExistenceCheck(
path: path,
fileSystem: swiftCommandState.fileSystem,
args: args,
env: env
)
#endif
// Parse json and return TestSuites.
return try TestSuite.parse(jsonString: data, context: args.joined(separator: " "))
}

/// Run a process and throw a more specific error if the file doesn't exist.
@discardableResult
private static func runProcessWithExistenceCheck(
path: AbsolutePath,
fileSystem: FileSystem,
args: [String],
env: Environment
) throws -> String {
do {
return try AsyncProcess.checkNonZeroExit(arguments: args, environment: env)
} catch {
// If the file doesn't exist, throw a more specific error.
if !fileSystem.exists(path) {
throw FileSystemError(.noEntry, path)
}
throw error
}
}

/// Creates the environment needed to test related tools.
static func constructTestEnvironment(
toolchain: UserToolchain,
Expand Down
10 changes: 10 additions & 0 deletions Tests/CommandsTests/TestCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,16 @@ final class TestCommandTests: CommandsTestCase {
}
}

func testListWithSkipBuildAndNoBuildArtifacts() async throws {
try await fixture(name: "Miscellaneous/TestDiscovery/Simple") { fixturePath in
await XCTAssertThrowsCommandExecutionError(
try await SwiftPM.Test.execute(["list", "--skip-build"], packagePath: fixturePath)
) { error in
XCTAssertMatch(error.stderr, .contains("Test build artifacts were not found in the build folder"))
}
}
}

func testBasicSwiftTestingIntegration() async throws {
#if !canImport(TestingDisabled)
try XCTSkipUnless(
Expand Down