@@ -9,6 +9,10 @@ import globals from '../../shared/extensionGlobals'
9
9
import { CrashMonitoring , ExtInstance , crashMonitoringStateFactory } from '../../shared/crashMonitoring'
10
10
import { isCI } from '../../shared/vscode/env'
11
11
import { getLogger } from '../../shared/logger/logger'
12
+ import { TimeLag } from '../../shared/utilities/timeoutUtils'
13
+ import { SinonSandbox , createSandbox } from 'sinon'
14
+ import { fs } from '../../shared'
15
+ import path from 'path'
12
16
13
17
class TestCrashMonitoring extends CrashMonitoring {
14
18
public constructor ( ...deps : ConstructorParameters < typeof CrashMonitoring > ) {
@@ -24,6 +28,8 @@ class TestCrashMonitoring extends CrashMonitoring {
24
28
export const crashMonitoringTest = async ( ) => {
25
29
let testFolder : TestFolder
26
30
let spawnedExtensions : TestCrashMonitoring [ ]
31
+ let timeLag : TimeLag
32
+ let sandbox : SinonSandbox
27
33
28
34
// Scale down the default interval we heartbeat and check for crashes to something much short for testing.
29
35
const checkInterval = 200
@@ -38,48 +44,54 @@ export const crashMonitoringTest = async () => {
38
44
* 1:1 mapping between Crash Reporting instances and the Extension instances.
39
45
*/
40
46
async function makeTestExtensions ( amount : number ) {
41
- const devLogger = getLogger ( )
42
-
43
47
const extensions : TestExtension [ ] = [ ]
44
48
for ( let i = 0 ; i < amount ; i ++ ) {
45
- const sessionId = `sessionId-${ i } `
46
- const pid = Number ( String ( i ) . repeat ( 6 ) )
47
- const state = await crashMonitoringStateFactory ( {
48
- workDirPath : testFolder . path ,
49
- isStateStale : async ( ) => false ,
50
- pid,
51
- sessionId : sessionId ,
52
- now : ( ) => globals . clock . Date . now ( ) ,
53
- memento : globals . globalState ,
54
- isDevMode : true ,
55
- devLogger,
56
- } )
57
- const ext = new TestCrashMonitoring ( state , checkInterval , true , false , devLogger )
58
- spawnedExtensions . push ( ext )
59
- const metadata = {
60
- extHostPid : pid ,
61
- sessionId,
62
- lastHeartbeat : globals . clock . Date . now ( ) ,
63
- isDebug : undefined ,
64
- }
65
- extensions [ i ] = { ext, metadata }
49
+ extensions [ i ] = await makeTestExtension ( i , new TimeLag ( ) )
66
50
}
67
51
return extensions
68
52
}
69
53
54
+ async function makeTestExtension ( id : number , timeLag : TimeLag , opts ?: { isStateStale : ( ) => Promise < boolean > } ) {
55
+ const isStateStale = opts ?. isStateStale ?? ( ( ) => Promise . resolve ( false ) )
56
+ const sessionId = `sessionId-${ id } `
57
+ const pid = Number ( String ( id ) . repeat ( 6 ) )
58
+
59
+ const state = await crashMonitoringStateFactory ( {
60
+ workDirPath : testFolder . path ,
61
+ isStateStale,
62
+ pid,
63
+ sessionId : sessionId ,
64
+ now : ( ) => globals . clock . Date . now ( ) ,
65
+ memento : globals . globalState ,
66
+ isDevMode : true ,
67
+ devLogger : getLogger ( ) ,
68
+ } )
69
+ const ext = new TestCrashMonitoring ( state , checkInterval , true , false , getLogger ( ) , timeLag )
70
+ spawnedExtensions . push ( ext )
71
+ const metadata = {
72
+ extHostPid : pid ,
73
+ sessionId,
74
+ lastHeartbeat : globals . clock . Date . now ( ) ,
75
+ isDebug : undefined ,
76
+ }
77
+ return { ext, metadata }
78
+ }
79
+
70
80
beforeEach ( async function ( ) {
71
81
testFolder = await TestFolder . create ( )
72
82
spawnedExtensions = [ ]
83
+ timeLag = new TimeLag ( )
84
+ sandbox = createSandbox ( )
73
85
} )
74
86
75
87
afterEach ( async function ( ) {
76
88
// clean up all running instances
77
89
spawnedExtensions ?. forEach ( ( e ) => e . crash ( ) )
90
+ timeLag . cleanup ( )
91
+ sandbox . restore ( )
78
92
} )
79
93
80
94
it ( 'graceful shutdown no metric emitted' , async function ( ) {
81
- // this.retries(3)
82
-
83
95
const exts = await makeTestExtensions ( 2 )
84
96
85
97
await exts [ 0 ] . ext . start ( )
@@ -95,9 +107,7 @@ export const crashMonitoringTest = async () => {
95
107
assertTelemetry ( 'session_end' , [ ] )
96
108
} )
97
109
98
- it ( 'single running instances crashes, so nothing is reported, but a new instaces appears and reports' , async function ( ) {
99
- // this.retries(3)
100
-
110
+ it ( 'single running instance crashes, so nothing is reported, but a new instaces appears and reports' , async function ( ) {
101
111
const exts = await makeTestExtensions ( 2 )
102
112
103
113
await exts [ 0 ] . ext . start ( )
@@ -112,34 +122,11 @@ export const crashMonitoringTest = async () => {
112
122
assertCrashedExtensions ( [ exts [ 0 ] ] )
113
123
} )
114
124
115
- it ( 'start the first extension, then start many subsequent ones and crash them all at once' , async function ( ) {
116
- // this.retries(3)
117
- const latestCrashedExts : TestExtension [ ] = [ ]
118
-
119
- const extCount = 10
120
- const exts = await makeTestExtensions ( extCount )
121
- for ( let i = 0 ; i < extCount ; i ++ ) {
122
- await exts [ i ] . ext . start ( )
123
- }
124
-
125
- // Crash all exts except the 0th one
126
- for ( let i = 1 ; i < extCount ; i ++ ) {
127
- await exts [ i ] . ext . crash ( )
128
- latestCrashedExts . push ( exts [ i ] )
129
- }
130
-
131
- // Give some extra time since there is a lot of file i/o
132
- await awaitIntervals ( oneInterval * 2 )
133
-
134
- assertCrashedExtensions ( latestCrashedExts )
135
- } )
136
-
137
- it ( 'the Primary checker crashes and another checker is promoted to Primary' , async function ( ) {
138
- // this.retries(3)
125
+ it ( 'multiple running instances start+crash at different times, but another instance always reports' , async function ( ) {
139
126
const latestCrashedExts : TestExtension [ ] = [ ]
140
127
141
128
const exts = await makeTestExtensions ( 4 )
142
- // Ext 0 is the Primary checker
129
+
143
130
await exts [ 0 ] . ext . start ( )
144
131
await awaitIntervals ( oneInterval )
145
132
@@ -166,6 +153,73 @@ export const crashMonitoringTest = async () => {
166
153
assertCrashedExtensions ( latestCrashedExts )
167
154
} )
168
155
156
+ it ( 'clears the state when a new os session is determined' , async function ( ) {
157
+ const exts = await makeTestExtensions ( 1 )
158
+
159
+ // Start an extension then crash it
160
+ await exts [ 0 ] . ext . start ( )
161
+ await exts [ 0 ] . ext . crash ( )
162
+ await awaitIntervals ( oneInterval )
163
+ // There is no other active instance to report the issue
164
+ assertTelemetry ( 'session_end' , [ ] )
165
+
166
+ // This extension clears the state due to it being stale, not reporting the previously crashed ext
167
+ const ext1 = await makeTestExtension ( 1 , timeLag , { isStateStale : ( ) => Promise . resolve ( true ) } )
168
+ await ext1 . ext . start ( )
169
+ await awaitIntervals ( oneInterval * 1 )
170
+ assertCrashedExtensions ( [ ] )
171
+ } )
172
+
173
+ it ( 'start the first extension, then start many subsequent ones and crash them all at once' , async function ( ) {
174
+ const latestCrashedExts : TestExtension [ ] = [ ]
175
+
176
+ const extCount = 10
177
+ const exts = await makeTestExtensions ( extCount )
178
+ for ( let i = 0 ; i < extCount ; i ++ ) {
179
+ await exts [ i ] . ext . start ( )
180
+ }
181
+
182
+ // Crash all exts except the 0th one
183
+ for ( let i = 1 ; i < extCount ; i ++ ) {
184
+ await exts [ i ] . ext . crash ( )
185
+ latestCrashedExts . push ( exts [ i ] )
186
+ }
187
+
188
+ // Give some extra time since there is a lot of file i/o
189
+ await awaitIntervals ( oneInterval * 2 )
190
+
191
+ assertCrashedExtensions ( latestCrashedExts )
192
+ } )
193
+
194
+ it ( 'does not check for crashes when there is a time lag' , async function ( ) {
195
+ // This test handles the case for a users computer doing a sleep+wake and
196
+ // then a crash was incorrectly reported since a new heartbeat could not be sent in time
197
+
198
+ const timeLagStub = sandbox . stub ( timeLag )
199
+ timeLagStub . start . resolves ( )
200
+ timeLagStub . didLag . resolves ( false )
201
+
202
+ // Load up a crash
203
+ const ext0 = await makeTestExtension ( 0 , timeLagStub as unknown as TimeLag )
204
+ await ext0 . ext . start ( )
205
+ await ext0 . ext . crash ( )
206
+
207
+ const ext1 = await makeTestExtension ( 1 , timeLagStub as unknown as TimeLag )
208
+ await ext1 . ext . start ( )
209
+
210
+ // Indicate that we have a time lag, and until it returns false
211
+ // we will skip crash checking
212
+ timeLagStub . didLag . resolves ( true )
213
+ await awaitIntervals ( oneInterval )
214
+ assertCrashedExtensions ( [ ] )
215
+ await awaitIntervals ( oneInterval )
216
+ assertCrashedExtensions ( [ ] )
217
+ // Now that the time lag is true, we will check for a crash
218
+ timeLagStub . didLag . resolves ( false )
219
+ await awaitIntervals ( oneInterval )
220
+ assertCrashedExtensions ( [ ext0 ] )
221
+ } )
222
+
169
223
/**
170
224
* Something like the following code can switch contexts early and the test will
171
225
* finish before it has completed. Certain async functions that may take longer to run
@@ -219,6 +273,31 @@ export const crashMonitoringTest = async () => {
219
273
function deduplicate < T > ( array : T [ ] , predicate : ( a : T , b : T ) => boolean ) : T [ ] {
220
274
return array . filter ( ( item , index , self ) => index === self . findIndex ( ( t ) => predicate ( item , t ) ) )
221
275
}
276
+
277
+ describe ( 'FileSystemState' , async function ( ) {
278
+ it ( 'ignores irrelevant files in state' , async function ( ) {
279
+ const state = await crashMonitoringStateFactory ( {
280
+ workDirPath : testFolder . path ,
281
+ isStateStale : ( ) => Promise . resolve ( false ) ,
282
+ pid : 1111 ,
283
+ sessionId : 'sessionId_1111' ,
284
+ now : ( ) => globals . clock . Date . now ( ) ,
285
+ memento : globals . globalState ,
286
+ isDevMode : true ,
287
+ devLogger : getLogger ( ) ,
288
+ } )
289
+ const stateDirPath = state . stateDirPath
290
+
291
+ assert . deepStrictEqual ( ( await fs . readdir ( stateDirPath ) ) . length , 0 )
292
+ await fs . writeFile ( path . join ( stateDirPath , 'ignoreMe.json' ) , '' )
293
+ await fs . mkdir ( path . join ( stateDirPath , 'ignoreMe' ) )
294
+ await state . sendHeartbeat ( ) // creates a relevant file in the state
295
+ assert . deepStrictEqual ( ( await fs . readdir ( stateDirPath ) ) . length , 3 )
296
+
297
+ const result = await state . getAllExts ( )
298
+ assert . deepStrictEqual ( result . length , 1 )
299
+ } )
300
+ } )
222
301
}
223
302
// This test is slow, so we only want to run it locally and not in CI. It will be run in the integ CI tests though.
224
303
; ( isCI ( ) ? describe . skip : describe ) ( 'CrashReporting' , crashMonitoringTest )
0 commit comments