@@ -206,5 +206,64 @@ final class CrashRecoveryTests: XCTestCase {
206
206
XCTAssertEqual ( postCrashHighlightResponse, expectedHighlightResponse)
207
207
} ( ) )
208
208
}
209
+
210
+ func testPreventClangdCrashLoop( ) {
211
+ guard longTestsEnabled else {
212
+ return
213
+ }
214
+
215
+ let ws = try ! staticSourceKitTibsWorkspace ( name: " ClangCrashRecovery " ) !
216
+ let loc = ws. testLoc ( " loc " )
217
+
218
+ try ! ws. openDocument ( loc. url, language: . cpp)
219
+
220
+ // Send a non-sential request to wait for clangd to start up
221
+
222
+ let hoverRequest = HoverRequest ( textDocument: loc. docIdentifier, position: Position ( line: 1 , utf16index: 6 ) )
223
+ _ = try ! ws. sk. sendSync ( hoverRequest)
224
+
225
+ // Start crashing clangd
226
+
227
+ let clangdServer = ws. testServer. server!. languageService ( for: loc. docUri, . cpp, in: ws. testServer. server!. workspace!) !
228
+
229
+ let clangdCrashed = self . expectation ( description: " clangd crashed " )
230
+ clangdCrashed. assertForOverFulfill = false
231
+ let clangdRestartedFirstTime = self . expectation ( description: " clangd restarted for the first time " )
232
+ let clangdRestartedSecondTime = self . expectation ( description: " clangd restarted for the second time " )
233
+ clangdRestartedSecondTime. assertForOverFulfill = false
234
+ var clangdHasRestartedFirstTime = false
235
+
236
+ clangdServer. addStateChangeHandler { ( oldState, newState) in
237
+ switch newState {
238
+ case . connectionInterrupted:
239
+ clangdCrashed. fulfill ( )
240
+ case . connected:
241
+ if !clangdHasRestartedFirstTime {
242
+ clangdRestartedFirstTime. fulfill ( )
243
+ clangdHasRestartedFirstTime = true
244
+ } else {
245
+ clangdRestartedSecondTime. fulfill ( )
246
+ }
247
+ default :
248
+ break
249
+ }
250
+ }
251
+
252
+ // Add a pragma to crash clang
253
+
254
+ let addCrashPragma = TextDocumentContentChangeEvent ( range: loc. position..< loc. position, rangeLength: 0 , text: " #pragma clang __debug crash \n " )
255
+ ws. sk. send ( DidChangeTextDocumentNotification ( textDocument: VersionedTextDocumentIdentifier ( loc. docUri, version: 3 ) , contentChanges: [ addCrashPragma] ) )
256
+
257
+ self . wait ( for: [ clangdCrashed] , timeout: 5 )
258
+
259
+ // Clangd has crashed for the first time. Leave the pragma there to crash clangd once again.
260
+
261
+ self . wait ( for: [ clangdRestartedFirstTime] , timeout: 30 )
262
+ // Clangd has restarted. Note the date so we can check that the second restart doesn't happen too quickly.
263
+ let firstRestartDate = Date ( )
264
+
265
+ self . wait ( for: [ clangdRestartedSecondTime] , timeout: 30 )
266
+ XCTAssert ( Date ( ) . timeIntervalSince ( firstRestartDate) > 5 , " Clangd restarted too quickly after crashing twice in a row. We are not preventing crash loops. " )
267
+ }
209
268
}
210
269
0 commit comments