From 1cd1102d896dca797f994339dc1f6e959ca3617f Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 11 Mar 2025 09:10:54 -0700 Subject: [PATCH 1/4] Introduce `defaultTimeoutDuration` to get the default timeout as a `Duration` --- .../Diagnose/TraceFromSignpostsCommand.swift | 2 -- .../RepeatUntilExpectedResult.swift | 2 +- Sources/SKTestSupport/SkipUnless.swift | 2 +- .../TestSourceKitLSPClient.swift | 8 +++---- Sources/SKTestSupport/Timeouts.swift | 2 ++ Tests/SourceKitDTests/SourceKitDTests.swift | 4 ++-- .../SwiftSourceKitPluginTests.swift | 22 +++++++++---------- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Sources/Diagnose/TraceFromSignpostsCommand.swift b/Sources/Diagnose/TraceFromSignpostsCommand.swift index a0aff8bfa..00dd22809 100644 --- a/Sources/Diagnose/TraceFromSignpostsCommand.swift +++ b/Sources/Diagnose/TraceFromSignpostsCommand.swift @@ -15,8 +15,6 @@ import Foundation import RegexBuilder import SwiftExtensions -import class TSCBasic.Process - /// Shared instance of the regex that is used to extract Signpost lines from `log stream --signpost`. fileprivate struct LogParseRegex { @MainActor static let shared = LogParseRegex() diff --git a/Sources/SKTestSupport/RepeatUntilExpectedResult.swift b/Sources/SKTestSupport/RepeatUntilExpectedResult.swift index 0ba30cbb2..412aa4428 100644 --- a/Sources/SKTestSupport/RepeatUntilExpectedResult.swift +++ b/Sources/SKTestSupport/RepeatUntilExpectedResult.swift @@ -21,7 +21,7 @@ import XCTest /// /// `sleepInterval` is the duration to wait before re-executing the body. package func repeatUntilExpectedResult( - timeout: Duration = .seconds(defaultTimeout), + timeout: Duration = defaultTimeoutDuration, sleepInterval: Duration = .seconds(1), _ body: () async throws -> Bool, file: StaticString = #filePath, diff --git a/Sources/SKTestSupport/SkipUnless.swift b/Sources/SKTestSupport/SkipUnless.swift index ae2fcfaee..2242cb54a 100644 --- a/Sources/SKTestSupport/SkipUnless.swift +++ b/Sources/SKTestSupport/SkipUnless.swift @@ -266,7 +266,7 @@ package actor SkipUnless { sourcekitd.keys.useNewAPI: 1 ], ]), - timeout: .seconds(defaultTimeout), + timeout: defaultTimeoutDuration, fileContents: nil ) return response[sourcekitd.keys.useNewAPI] == 1 diff --git a/Sources/SKTestSupport/TestSourceKitLSPClient.swift b/Sources/SKTestSupport/TestSourceKitLSPClient.swift index 52ae013ef..065e207a8 100644 --- a/Sources/SKTestSupport/TestSourceKitLSPClient.swift +++ b/Sources/SKTestSupport/TestSourceKitLSPClient.swift @@ -197,7 +197,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { preInitialization?(self) if initialize { let capabilities = capabilities - try await withTimeout(.seconds(defaultTimeout)) { + try await withTimeout(defaultTimeoutDuration) { _ = try await self.send( InitializeRequest( processId: nil, @@ -270,7 +270,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { /// /// - Note: This also returns any notifications sent before the call to /// `nextNotification`. - package func nextNotification(timeout: Duration = .seconds(defaultTimeout)) async throws -> any NotificationType { + package func nextNotification(timeout: Duration = defaultTimeoutDuration) async throws -> any NotificationType { return try await notifications.next(timeout: timeout) } @@ -279,7 +279,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { /// If the next notification is not a `PublishDiagnosticsNotification`, this /// methods throws. package func nextDiagnosticsNotification( - timeout: Duration = .seconds(defaultTimeout) + timeout: Duration = defaultTimeoutDuration ) async throws -> PublishDiagnosticsNotification { guard !usePullDiagnostics else { struct PushDiagnosticsError: Error, CustomStringConvertible { @@ -295,7 +295,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable { package func nextNotification( ofType: ExpectedNotificationType.Type, satisfying predicate: (ExpectedNotificationType) throws -> Bool = { _ in true }, - timeout: Duration = .seconds(defaultTimeout) + timeout: Duration = defaultTimeoutDuration ) async throws -> ExpectedNotificationType { while true { let nextNotification = try await nextNotification(timeout: timeout) diff --git a/Sources/SKTestSupport/Timeouts.swift b/Sources/SKTestSupport/Timeouts.swift index c1f66e6c4..e536fb98b 100644 --- a/Sources/SKTestSupport/Timeouts.swift +++ b/Sources/SKTestSupport/Timeouts.swift @@ -22,3 +22,5 @@ package let defaultTimeout: TimeInterval = { } return 180 }() + +package var defaultTimeoutDuration: Duration { .seconds(defaultTimeout) } diff --git a/Tests/SourceKitDTests/SourceKitDTests.swift b/Tests/SourceKitDTests/SourceKitDTests.swift index b119fa79c..390ee5100 100644 --- a/Tests/SourceKitDTests/SourceKitDTests.swift +++ b/Tests/SourceKitDTests/SourceKitDTests.swift @@ -84,7 +84,7 @@ final class SourceKitDTests: XCTestCase { keys.compilerArgs: args, ]) - _ = try await sourcekitd.send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await sourcekitd.send(req, timeout: defaultTimeoutDuration, fileContents: nil) try await fulfillmentOfOrThrow([expectation1, expectation2]) @@ -92,7 +92,7 @@ final class SourceKitDTests: XCTestCase { keys.request: sourcekitd.requests.editorClose, keys.name: path, ]) - _ = try await sourcekitd.send(close, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await sourcekitd.send(close, timeout: defaultTimeoutDuration, fileContents: nil) } } diff --git a/Tests/SwiftSourceKitPluginTests/SwiftSourceKitPluginTests.swift b/Tests/SwiftSourceKitPluginTests/SwiftSourceKitPluginTests.swift index 9f7e510f1..a411ea989 100644 --- a/Tests/SwiftSourceKitPluginTests/SwiftSourceKitPluginTests.swift +++ b/Tests/SwiftSourceKitPluginTests/SwiftSourceKitPluginTests.swift @@ -1826,7 +1826,7 @@ fileprivate extension SourceKitD { keys.syntacticOnly: 1, keys.compilerArgs: compilerArguments as [SKDRequestValue], ]) - _ = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) return DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarkers) } @@ -1840,7 +1840,7 @@ fileprivate extension SourceKitD { keys.syntacticOnly: 1, ]) - _ = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) } func closeDocument(_ name: String) async throws { @@ -1849,7 +1849,7 @@ fileprivate extension SourceKitD { keys.name: name, ]) - _ = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) } func completeImpl( @@ -1884,7 +1884,7 @@ fileprivate extension SourceKitD { keys.compilerArgs: compilerArguments as [SKDRequestValue]?, ]) - let res = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let res = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) return try CompletionResultSet(res) } @@ -1942,7 +1942,7 @@ fileprivate extension SourceKitD { keys.codeCompleteOptions: dictionary([keys.useNewAPI: 1]), ]) - _ = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) } func completeDocumentation(id: Int) async throws -> CompletionDocumentation { @@ -1951,7 +1951,7 @@ fileprivate extension SourceKitD { keys.identifier: id, ]) - let resp = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let resp = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) return CompletionDocumentation(resp) } @@ -1960,7 +1960,7 @@ fileprivate extension SourceKitD { keys.request: requests.codeCompleteDiagnostic, keys.identifier: id, ]) - let resp = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let resp = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) return CompletionDiagnostic(resp) } @@ -1969,7 +1969,7 @@ fileprivate extension SourceKitD { let req = dictionary([ keys.request: requests.dependencyUpdated ]) - _ = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + _ = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) } func setPopularAPI(popular: [String], unpopular: [String]) async throws { @@ -1980,7 +1980,7 @@ fileprivate extension SourceKitD { keys.unpopular: unpopular as [SKDRequestValue], ]) - let resp = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let resp = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) XCTAssertEqual(resp[keys.useNewAPI], 1) } @@ -1997,7 +1997,7 @@ fileprivate extension SourceKitD { keys.notoriousModules: notoriousModules as [SKDRequestValue], ]) - let resp = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let resp = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) XCTAssertEqual(resp[keys.useNewAPI], 1) } @@ -2021,7 +2021,7 @@ fileprivate extension SourceKitD { keys.modulePopularity: modulePopularity as [SKDRequestValue], ]) - let resp = try await send(req, timeout: .seconds(defaultTimeout), fileContents: nil) + let resp = try await send(req, timeout: defaultTimeoutDuration, fileContents: nil) XCTAssertEqual(resp[keys.useNewAPI], 1) } From 572660ae26475de6ca95c7b00f51b1c45b449b29 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 11 Mar 2025 09:12:33 -0700 Subject: [PATCH 2/4] Add timeouts to most subprocess calls in tests Just a speculative fix to avoid timeouts during the `swift test` invocation --- .../IndexedSingleSwiftFileTestProject.swift | 7 ++++- Sources/SKTestSupport/SkipUnless.swift | 29 ++++++++++--------- .../SwiftPMDependencyProject.swift | 12 ++++---- .../SKTestSupport/SwiftPMTestProject.swift | 22 ++++++++++++-- .../Swift/DocumentFormatting.swift | 4 ++- .../BackgroundIndexingTests.swift | 24 ++++++++++----- Tests/SourceKitLSPTests/WorkspaceTests.swift | 25 +++++++--------- .../TSCExtensionsTests/ProcessRunTests.swift | 10 ++++--- 8 files changed, 86 insertions(+), 47 deletions(-) diff --git a/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift b/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift index 16b241be7..78c4d73de 100644 --- a/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift +++ b/Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift @@ -13,6 +13,7 @@ @_spi(Testing) import BuildSystemIntegration package import Foundation package import LanguageServerProtocol +import SKLogging import SKOptions import SourceKitLSP import SwiftExtensions @@ -126,7 +127,11 @@ package struct IndexedSingleSwiftFileTestProject { // Run swiftc to build the index store do { - try await Process.checkNonZeroExit(arguments: [swiftc.filePath] + compilerArguments) + let compilerArgumentsCopy = compilerArguments + let output = try await withTimeout(defaultTimeoutDuration) { + try await Process.checkNonZeroExit(arguments: [swiftc.filePath] + compilerArgumentsCopy) + } + logger.debug("swiftc output:\n\(output)") } catch { if !allowBuildFailure { throw error diff --git a/Sources/SKTestSupport/SkipUnless.swift b/Sources/SKTestSupport/SkipUnless.swift index 2242cb54a..0ca68d627 100644 --- a/Sources/SKTestSupport/SkipUnless.swift +++ b/Sources/SKTestSupport/SkipUnless.swift @@ -222,19 +222,22 @@ package actor SkipUnless { throw FailedToCrateInputFileError() } // If we can't compile for wasm, this fails complaining that it can't find the stdlib for wasm. - let process = Process( - args: try swiftFrontend.filePath, - "-typecheck", - try input.filePath, - "-triple", - "wasm32-unknown-none-wasm", - "-enable-experimental-feature", - "Embedded", - "-Xcc", - "-fdeclspec" - ) - try process.launch() - let result = try await process.waitUntilExit() + let result = try await withTimeout(defaultTimeoutDuration) { + try await Process.run( + arguments: [ + try swiftFrontend.filePath, + "-typecheck", + try input.filePath, + "-triple", + "wasm32-unknown-none-wasm", + "-enable-experimental-feature", + "Embedded", + "-Xcc", + "-fdeclspec", + ], + workingDirectory: nil + ) + } if result.exitStatus == .terminated(code: 0) { return .featureSupported } diff --git a/Sources/SKTestSupport/SwiftPMDependencyProject.swift b/Sources/SKTestSupport/SwiftPMDependencyProject.swift index 7e8128df0..79addaee9 100644 --- a/Sources/SKTestSupport/SwiftPMDependencyProject.swift +++ b/Sources/SKTestSupport/SwiftPMDependencyProject.swift @@ -13,6 +13,7 @@ package import Foundation import LanguageServerProtocolExtensions import SwiftExtensions +import TSCExtensions import XCTest import struct TSCBasic.AbsolutePath @@ -45,11 +46,12 @@ package class SwiftPMDependencyProject { } // We can't use `workingDirectory` because Amazon Linux doesn't support working directories (or at least // TSCBasic.Process doesn't support working directories on Amazon Linux) - let process = TSCBasic.Process( - arguments: [try git.filePath, "-C", try workingDirectory.filePath] + arguments - ) - try process.launch() - let processResult = try await process.waitUntilExit() + let processResult = try await withTimeout(defaultTimeoutDuration) { + try await TSCBasic.Process.run( + arguments: [try git.filePath, "-C", try workingDirectory.filePath] + arguments, + workingDirectory: nil + ) + } guard processResult.exitStatus == .terminated(code: 0) else { throw Error.processedTerminatedWithNonZeroExitCode(processResult) } diff --git a/Sources/SKTestSupport/SwiftPMTestProject.swift b/Sources/SKTestSupport/SwiftPMTestProject.swift index d49938f38..bf1a92750 100644 --- a/Sources/SKTestSupport/SwiftPMTestProject.swift +++ b/Sources/SKTestSupport/SwiftPMTestProject.swift @@ -12,6 +12,7 @@ package import Foundation package import LanguageServerProtocol +import SKLogging package import SKOptions package import SourceKitLSP import SwiftExtensions @@ -260,7 +261,16 @@ package class SwiftPMTestProject: MultiFileTestProject { "-Xswiftc", "-module-cache-path", "-Xswiftc", try globalModuleCache.filePath, ] } - try await Process.checkNonZeroExit(arguments: arguments) + let argumentsCopy = arguments + let output = try await withTimeout(defaultTimeoutDuration) { + try await Process.checkNonZeroExit(arguments: argumentsCopy) + } + logger.debug( + """ + 'swift build' output: + \(output) + """ + ) } /// Resolve package dependencies for the package at `path`. @@ -274,6 +284,14 @@ package class SwiftPMTestProject: MultiFileTestProject { "resolve", "--package-path", try path.filePath, ] - try await Process.checkNonZeroExit(arguments: arguments) + let output = try await withTimeout(defaultTimeoutDuration) { + try await Process.checkNonZeroExit(arguments: arguments) + } + logger.debug( + """ + 'swift package resolve' output: + \(output) + """ + ) } } diff --git a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift index 30aaf9cb5..ba45f6284 100644 --- a/Sources/SourceKitLSP/Swift/DocumentFormatting.swift +++ b/Sources/SourceKitLSP/Swift/DocumentFormatting.swift @@ -196,7 +196,9 @@ extension SwiftLanguageService { writeStream.send(snapshot.text) try writeStream.close() - let result = try await process.waitUntilExitStoppingProcessOnTaskCancellation() + let result = try await withTimeout(.seconds(60)) { + try await process.waitUntilExitStoppingProcessOnTaskCancellation() + } guard result.exitStatus == .terminated(code: 0) else { let swiftFormatErrorMessage: String switch result.stderrOutput { diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 69d3cf854..33b8892a4 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -1353,14 +1353,24 @@ final class BackgroundIndexingTests: XCTestCase { // - The user runs `swift package update` // - This updates `Package.resolved`, which we watch // - We reload the package, which updates `Dependency.swift` in `.build/index-build/checkouts`, which we also watch. - try await Process.run( - arguments: [ - unwrap(ToolchainRegistry.forTesting.default?.swift?.filePath), - "package", "update", - "--package-path", project.scratchDirectory.filePath, - ], - workingDirectory: nil + let projectURL = project.scratchDirectory + let packageUpdateOutput = try await withTimeout(defaultTimeoutDuration) { + try await Process.run( + arguments: [ + unwrap(ToolchainRegistry.forTesting.default?.swift?.filePath), + "package", "update", + "--package-path", projectURL.filePath, + ], + workingDirectory: nil + ) + } + logger.debug( + """ + 'swift package update' output: + \(packageUpdateOutput) + """ ) + XCTAssertNotEqual(try String(contentsOf: packageResolvedURL, encoding: .utf8), originalPackageResolvedContents) project.testClient.send( DidChangeWatchedFilesNotification(changes: [ diff --git a/Tests/SourceKitLSPTests/WorkspaceTests.swift b/Tests/SourceKitLSPTests/WorkspaceTests.swift index abede39f9..c5e1d03ca 100644 --- a/Tests/SourceKitLSPTests/WorkspaceTests.swift +++ b/Tests/SourceKitLSPTests/WorkspaceTests.swift @@ -690,13 +690,7 @@ final class WorkspaceTests: XCTestCase { let packageDir = try project.uri(for: "Package.swift").fileURL!.deletingLastPathComponent() - try await TSCBasic.Process.checkNonZeroExit(arguments: [ - ToolchainRegistry.forTesting.default!.swift!.filePath, - "build", - "--package-path", packageDir.filePath, - "-Xswiftc", "-index-ignore-system-modules", - "-Xcc", "-index-ignore-system-symbols", - ]) + try await SwiftPMTestProject.build(at: packageDir) let (otherPackageUri, positions) = try project.openDocument("otherPackage.swift") let testPosition = positions["1️⃣"] @@ -1356,13 +1350,16 @@ final class WorkspaceTests: XCTestCase { ) let clang = try unwrap(await ToolchainRegistry.forTesting.default?.clang) - try await Process.checkNonZeroExit( - arguments: [ - clang.filePath, "-index-store-path", scratchDirectory.appendingPathComponent("index").filePath, - scratchDirectory.appendingPathComponent("test.c").filePath, - "-fsyntax-only", - ] - ) + let clangOutput = try await withTimeout(defaultTimeoutDuration) { + try await Process.checkNonZeroExit( + arguments: [ + clang.filePath, "-index-store-path", scratchDirectory.appendingPathComponent("index").filePath, + scratchDirectory.appendingPathComponent("test.c").filePath, + "-fsyntax-only", + ] + ) + } + logger.debug("Clang output:\n\(clangOutput)") let testClient = try await TestSourceKitLSPClient( options: .testDefault(experimentalFeatures: [.sourceKitOptionsRequest]), diff --git a/Tests/TSCExtensionsTests/ProcessRunTests.swift b/Tests/TSCExtensionsTests/ProcessRunTests.swift index ddca3e02b..92bc4b004 100644 --- a/Tests/TSCExtensionsTests/ProcessRunTests.swift +++ b/Tests/TSCExtensionsTests/ProcessRunTests.swift @@ -38,10 +38,12 @@ final class ProcessRunTests: XCTestCase { print(os.getcwd(), end='') """.write(to: pythonFile, atomically: true, encoding: .utf8) - let result = try await Process.run( - arguments: [python.filePath, pythonFile.filePath], - workingDirectory: AbsolutePath(validating: workingDir.filePath) - ) + let result = try await withTimeout(defaultTimeoutDuration) { + try await Process.run( + arguments: [python.filePath, pythonFile.filePath], + workingDirectory: AbsolutePath(validating: workingDir.filePath) + ) + } let stdout = try unwrap(String(bytes: result.output.get(), encoding: .utf8)) XCTAssertEqual(stdout, try workingDir.filePath) } From ca960bc9c296877b33f80f2e9794d2bb926d0099 Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 11 Mar 2025 09:14:26 -0700 Subject: [PATCH 3/4] Introduce a `MultiEntrySemaphore` for testing Compare to the `while !AtomicBool.value { sleep }` pattern, this eventually times out, avoiding a timeout of the entire `swift test` invocation. --- .../SKTestSupport/MultiEntrySemaphore.swift | 52 +++++++++++++++++++ .../BackgroundIndexingTests.swift | 8 ++- .../PullDiagnosticsTests.swift | 13 ++--- 3 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 Sources/SKTestSupport/MultiEntrySemaphore.swift diff --git a/Sources/SKTestSupport/MultiEntrySemaphore.swift b/Sources/SKTestSupport/MultiEntrySemaphore.swift new file mode 100644 index 000000000..1fd7316ca --- /dev/null +++ b/Sources/SKTestSupport/MultiEntrySemaphore.swift @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import SwiftExtensions +import XCTest + +/// A semaphore that, once signaled, will pass on every `wait` call. Ie. the semaphore only needs to be signaled once +/// and from that point onwards it can be acquired as many times as necessary. +/// +/// Use cases of this are for example to delay indexing until a some other task has been performed. But once that is +/// done, all index operations should be able to run, not just one. +package final class MultiEntrySemaphore: Sendable { + private let name: String + private let signaled = AtomicBool(initialValue: false) + + package init(name: String) { + self.name = name + } + + package func signal() { + signaled.value = true + } + + package func waitOrThrow() async throws { + do { + try await repeatUntilExpectedResult(sleepInterval: .seconds(0.01)) { signaled.value } + } catch { + struct TimeoutError: Error, CustomStringConvertible { + let name: String + var description: String { "\(name) timed out" } + } + throw TimeoutError(name: "\(name) timed out") + } + } + + package func waitOrXCTFail(file: StaticString = #filePath, line: UInt = #line) async { + do { + try await waitOrThrow() + } catch { + XCTFail("\(error)", file: file, line: line) + } + } +} diff --git a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift index 33b8892a4..414559283 100644 --- a/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift +++ b/Tests/SourceKitLSPTests/BackgroundIndexingTests.swift @@ -1937,12 +1937,10 @@ final class BackgroundIndexingTests: XCTestCase { } func testIsIndexingRequest() async throws { - let checkedIsIndexStatus = AtomicBool(initialValue: false) + let checkedIsIndexStatus = MultiEntrySemaphore(name: "Checked is index status") let hooks = Hooks( indexHooks: IndexHooks(updateIndexStoreTaskDidStart: { task in - while !checkedIsIndexStatus.value { - try? await Task.sleep(for: .seconds(0.1)) - } + await checkedIsIndexStatus.waitOrXCTFail() }) ) let project = try await SwiftPMTestProject( @@ -1956,7 +1954,7 @@ final class BackgroundIndexingTests: XCTestCase { ) let isIndexingResponseWhileIndexing = try await project.testClient.send(IsIndexingRequest()) XCTAssert(isIndexingResponseWhileIndexing.indexing) - checkedIsIndexStatus.value = true + checkedIsIndexStatus.signal() try await repeatUntilExpectedResult { try await project.testClient.send(IsIndexingRequest()).indexing == false diff --git a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift index 42236a676..a7296023e 100644 --- a/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift +++ b/Tests/SourceKitLSPTests/PullDiagnosticsTests.swift @@ -279,20 +279,13 @@ final class PullDiagnosticsTests: XCTestCase { } func testDiagnosticsWaitForDocumentToBePrepared() async throws { - let diagnosticRequestSent = AtomicBool(initialValue: false) + let diagnosticRequestSent = MultiEntrySemaphore(name: "Diagnostic request sent") var testHooks = Hooks() testHooks.indexHooks.preparationTaskDidStart = { @Sendable taskDescription in // Only start preparation after we sent the diagnostic request. In almost all cases, this should not give // preparation enough time to finish before the diagnostic request is handled unless we wait for preparation in // the diagnostic request. - while diagnosticRequestSent.value == false { - do { - try await Task.sleep(for: .seconds(0.01)) - } catch { - XCTFail("Did not expect sleep to fail") - break - } - } + await diagnosticRequestSent.waitOrXCTFail() } let project = try await SwiftPMTestProject( @@ -331,7 +324,7 @@ final class PullDiagnosticsTests: XCTestCase { XCTAssertEqual(diagnostics.success?.fullReport?.items, []) receivedDiagnostics.fulfill() } - diagnosticRequestSent.value = true + diagnosticRequestSent.signal() try await fulfillmentOfOrThrow([receivedDiagnostics]) } From 9cd599cc61dc56c00620997a92c1bfb54d6822af Mon Sep 17 00:00:00 2001 From: Alex Hoppen Date: Tue, 11 Mar 2025 09:14:47 -0700 Subject: [PATCH 4/4] Miscellaneous test updates --- .../CompilationDatabaseTests.swift | 13 ++----------- Tests/SourceKitLSPTests/WorkspaceTests.swift | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/Tests/SourceKitLSPTests/CompilationDatabaseTests.swift b/Tests/SourceKitLSPTests/CompilationDatabaseTests.swift index 36fbf2f20..fe1a91b29 100644 --- a/Tests/SourceKitLSPTests/CompilationDatabaseTests.swift +++ b/Tests/SourceKitLSPTests/CompilationDatabaseTests.swift @@ -75,21 +75,12 @@ final class CompilationDatabaseTests: XCTestCase { DocumentHighlight(range: positions["5️⃣"]..