Skip to content

Commit 9c867df

Browse files
authored
Merge pull request #2057 from ahoppen/test-timeout
Add a few timeout checks to tests
2 parents f9fc58d + 9cd599c commit 9c867df

17 files changed

+168
-95
lines changed

Sources/Diagnose/TraceFromSignpostsCommand.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import Foundation
1515
import RegexBuilder
1616
import SwiftExtensions
1717

18-
import class TSCBasic.Process
19-
2018
/// Shared instance of the regex that is used to extract Signpost lines from `log stream --signpost`.
2119
fileprivate struct LogParseRegex {
2220
@MainActor static let shared = LogParseRegex()

Sources/SKTestSupport/IndexedSingleSwiftFileTestProject.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
@_spi(Testing) import BuildSystemIntegration
1414
package import Foundation
1515
package import LanguageServerProtocol
16+
import SKLogging
1617
import SKOptions
1718
import SourceKitLSP
1819
import SwiftExtensions
@@ -126,7 +127,11 @@ package struct IndexedSingleSwiftFileTestProject {
126127

127128
// Run swiftc to build the index store
128129
do {
129-
try await Process.checkNonZeroExit(arguments: [swiftc.filePath] + compilerArguments)
130+
let compilerArgumentsCopy = compilerArguments
131+
let output = try await withTimeout(defaultTimeoutDuration) {
132+
try await Process.checkNonZeroExit(arguments: [swiftc.filePath] + compilerArgumentsCopy)
133+
}
134+
logger.debug("swiftc output:\n\(output)")
130135
} catch {
131136
if !allowBuildFailure {
132137
throw error
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftExtensions
14+
import XCTest
15+
16+
/// A semaphore that, once signaled, will pass on every `wait` call. Ie. the semaphore only needs to be signaled once
17+
/// and from that point onwards it can be acquired as many times as necessary.
18+
///
19+
/// Use cases of this are for example to delay indexing until a some other task has been performed. But once that is
20+
/// done, all index operations should be able to run, not just one.
21+
package final class MultiEntrySemaphore: Sendable {
22+
private let name: String
23+
private let signaled = AtomicBool(initialValue: false)
24+
25+
package init(name: String) {
26+
self.name = name
27+
}
28+
29+
package func signal() {
30+
signaled.value = true
31+
}
32+
33+
package func waitOrThrow() async throws {
34+
do {
35+
try await repeatUntilExpectedResult(sleepInterval: .seconds(0.01)) { signaled.value }
36+
} catch {
37+
struct TimeoutError: Error, CustomStringConvertible {
38+
let name: String
39+
var description: String { "\(name) timed out" }
40+
}
41+
throw TimeoutError(name: "\(name) timed out")
42+
}
43+
}
44+
45+
package func waitOrXCTFail(file: StaticString = #filePath, line: UInt = #line) async {
46+
do {
47+
try await waitOrThrow()
48+
} catch {
49+
XCTFail("\(error)", file: file, line: line)
50+
}
51+
}
52+
}

Sources/SKTestSupport/RepeatUntilExpectedResult.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import XCTest
2121
///
2222
/// `sleepInterval` is the duration to wait before re-executing the body.
2323
package func repeatUntilExpectedResult(
24-
timeout: Duration = .seconds(defaultTimeout),
24+
timeout: Duration = defaultTimeoutDuration,
2525
sleepInterval: Duration = .seconds(1),
2626
_ body: () async throws -> Bool,
2727
file: StaticString = #filePath,

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -222,19 +222,22 @@ package actor SkipUnless {
222222
throw FailedToCrateInputFileError()
223223
}
224224
// If we can't compile for wasm, this fails complaining that it can't find the stdlib for wasm.
225-
let process = Process(
226-
args: try swiftFrontend.filePath,
227-
"-typecheck",
228-
try input.filePath,
229-
"-triple",
230-
"wasm32-unknown-none-wasm",
231-
"-enable-experimental-feature",
232-
"Embedded",
233-
"-Xcc",
234-
"-fdeclspec"
235-
)
236-
try process.launch()
237-
let result = try await process.waitUntilExit()
225+
let result = try await withTimeout(defaultTimeoutDuration) {
226+
try await Process.run(
227+
arguments: [
228+
try swiftFrontend.filePath,
229+
"-typecheck",
230+
try input.filePath,
231+
"-triple",
232+
"wasm32-unknown-none-wasm",
233+
"-enable-experimental-feature",
234+
"Embedded",
235+
"-Xcc",
236+
"-fdeclspec",
237+
],
238+
workingDirectory: nil
239+
)
240+
}
238241
if result.exitStatus == .terminated(code: 0) {
239242
return .featureSupported
240243
}
@@ -266,7 +269,7 @@ package actor SkipUnless {
266269
sourcekitd.keys.useNewAPI: 1
267270
],
268271
]),
269-
timeout: .seconds(defaultTimeout),
272+
timeout: defaultTimeoutDuration,
270273
fileContents: nil
271274
)
272275
return response[sourcekitd.keys.useNewAPI] == 1

Sources/SKTestSupport/SwiftPMDependencyProject.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
package import Foundation
1414
import LanguageServerProtocolExtensions
1515
import SwiftExtensions
16+
import TSCExtensions
1617
import XCTest
1718

1819
import struct TSCBasic.AbsolutePath
@@ -45,11 +46,12 @@ package class SwiftPMDependencyProject {
4546
}
4647
// We can't use `workingDirectory` because Amazon Linux doesn't support working directories (or at least
4748
// TSCBasic.Process doesn't support working directories on Amazon Linux)
48-
let process = TSCBasic.Process(
49-
arguments: [try git.filePath, "-C", try workingDirectory.filePath] + arguments
50-
)
51-
try process.launch()
52-
let processResult = try await process.waitUntilExit()
49+
let processResult = try await withTimeout(defaultTimeoutDuration) {
50+
try await TSCBasic.Process.run(
51+
arguments: [try git.filePath, "-C", try workingDirectory.filePath] + arguments,
52+
workingDirectory: nil
53+
)
54+
}
5355
guard processResult.exitStatus == .terminated(code: 0) else {
5456
throw Error.processedTerminatedWithNonZeroExitCode(processResult)
5557
}

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
package import Foundation
1414
package import LanguageServerProtocol
15+
import SKLogging
1516
package import SKOptions
1617
package import SourceKitLSP
1718
import SwiftExtensions
@@ -260,7 +261,16 @@ package class SwiftPMTestProject: MultiFileTestProject {
260261
"-Xswiftc", "-module-cache-path", "-Xswiftc", try globalModuleCache.filePath,
261262
]
262263
}
263-
try await Process.checkNonZeroExit(arguments: arguments)
264+
let argumentsCopy = arguments
265+
let output = try await withTimeout(defaultTimeoutDuration) {
266+
try await Process.checkNonZeroExit(arguments: argumentsCopy)
267+
}
268+
logger.debug(
269+
"""
270+
'swift build' output:
271+
\(output)
272+
"""
273+
)
264274
}
265275

266276
/// Resolve package dependencies for the package at `path`.
@@ -274,6 +284,14 @@ package class SwiftPMTestProject: MultiFileTestProject {
274284
"resolve",
275285
"--package-path", try path.filePath,
276286
]
277-
try await Process.checkNonZeroExit(arguments: arguments)
287+
let output = try await withTimeout(defaultTimeoutDuration) {
288+
try await Process.checkNonZeroExit(arguments: arguments)
289+
}
290+
logger.debug(
291+
"""
292+
'swift package resolve' output:
293+
\(output)
294+
"""
295+
)
278296
}
279297
}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
197197
preInitialization?(self)
198198
if initialize {
199199
let capabilities = capabilities
200-
try await withTimeout(.seconds(defaultTimeout)) {
200+
try await withTimeout(defaultTimeoutDuration) {
201201
_ = try await self.send(
202202
InitializeRequest(
203203
processId: nil,
@@ -270,7 +270,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
270270
///
271271
/// - Note: This also returns any notifications sent before the call to
272272
/// `nextNotification`.
273-
package func nextNotification(timeout: Duration = .seconds(defaultTimeout)) async throws -> any NotificationType {
273+
package func nextNotification(timeout: Duration = defaultTimeoutDuration) async throws -> any NotificationType {
274274
return try await notifications.next(timeout: timeout)
275275
}
276276

@@ -279,7 +279,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
279279
/// If the next notification is not a `PublishDiagnosticsNotification`, this
280280
/// methods throws.
281281
package func nextDiagnosticsNotification(
282-
timeout: Duration = .seconds(defaultTimeout)
282+
timeout: Duration = defaultTimeoutDuration
283283
) async throws -> PublishDiagnosticsNotification {
284284
guard !usePullDiagnostics else {
285285
struct PushDiagnosticsError: Error, CustomStringConvertible {
@@ -295,7 +295,7 @@ package final class TestSourceKitLSPClient: MessageHandler, Sendable {
295295
package func nextNotification<ExpectedNotificationType: NotificationType>(
296296
ofType: ExpectedNotificationType.Type,
297297
satisfying predicate: (ExpectedNotificationType) throws -> Bool = { _ in true },
298-
timeout: Duration = .seconds(defaultTimeout)
298+
timeout: Duration = defaultTimeoutDuration
299299
) async throws -> ExpectedNotificationType {
300300
while true {
301301
let nextNotification = try await nextNotification(timeout: timeout)

Sources/SKTestSupport/Timeouts.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ package let defaultTimeout: TimeInterval = {
2222
}
2323
return 180
2424
}()
25+
26+
package var defaultTimeoutDuration: Duration { .seconds(defaultTimeout) }

Sources/SourceKitLSP/Swift/DocumentFormatting.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,9 @@ extension SwiftLanguageService {
196196
writeStream.send(snapshot.text)
197197
try writeStream.close()
198198

199-
let result = try await process.waitUntilExitStoppingProcessOnTaskCancellation()
199+
let result = try await withTimeout(.seconds(60)) {
200+
try await process.waitUntilExitStoppingProcessOnTaskCancellation()
201+
}
200202
guard result.exitStatus == .terminated(code: 0) else {
201203
let swiftFormatErrorMessage: String
202204
switch result.stderrOutput {

0 commit comments

Comments
 (0)