1namespace ts.projectSystem { 2 describe("unittests:: tsserver:: cancellationToken", () => { 3 // Disable sourcemap support for the duration of the test, as sourcemapping the errors generated during this test is slow and not something we care to test 4 let oldPrepare: AnyFunction; 5 before(() => { 6 oldPrepare = (Error as any).prepareStackTrace; 7 delete (Error as any).prepareStackTrace; 8 }); 9 10 after(() => { 11 (Error as any).prepareStackTrace = oldPrepare; 12 }); 13 14 it("is attached to request", () => { 15 const f1 = { 16 path: "/a/b/app.ts", 17 content: "let xyz = 1;" 18 }; 19 const host = createServerHost([f1]); 20 let expectedRequestId: number; 21 const cancellationToken: server.ServerCancellationToken = { 22 isCancellationRequested: () => false, 23 setRequest: requestId => { 24 if (expectedRequestId === undefined) { 25 assert.isTrue(false, "unexpected call"); 26 } 27 assert.equal(requestId, expectedRequestId); 28 }, 29 resetRequest: noop 30 }; 31 32 const session = createSession(host, { cancellationToken }); 33 34 expectedRequestId = session.getNextSeq(); 35 session.executeCommandSeq(<server.protocol.OpenRequest>{ 36 command: "open", 37 arguments: { file: f1.path } 38 }); 39 40 expectedRequestId = session.getNextSeq(); 41 session.executeCommandSeq(<server.protocol.GeterrRequest>{ 42 command: "geterr", 43 arguments: { files: [f1.path] } 44 }); 45 46 expectedRequestId = session.getNextSeq(); 47 session.executeCommandSeq(<server.protocol.OccurrencesRequest>{ 48 command: "occurrences", 49 arguments: { file: f1.path, line: 1, offset: 6 } 50 }); 51 52 expectedRequestId = 2; 53 host.runQueuedImmediateCallbacks(); 54 expectedRequestId = 2; 55 host.runQueuedImmediateCallbacks(); 56 }); 57 58 it("Geterr is cancellable", () => { 59 const f1 = { 60 path: "/a/app.ts", 61 content: "let x = 1" 62 }; 63 const config = { 64 path: "/a/tsconfig.json", 65 content: JSON.stringify({ 66 compilerOptions: {} 67 }) 68 }; 69 70 const cancellationToken = new TestServerCancellationToken(); 71 const host = createServerHost([f1, config]); 72 const session = createSession(host, { 73 canUseEvents: true, 74 eventHandler: noop, 75 cancellationToken 76 }); 77 { 78 session.executeCommandSeq(<protocol.OpenRequest>{ 79 command: "open", 80 arguments: { file: f1.path } 81 }); 82 // send geterr for missing file 83 session.executeCommandSeq(<protocol.GeterrRequest>{ 84 command: "geterr", 85 arguments: { files: ["/a/missing"] } 86 }); 87 // Queued files 88 assert.equal(host.getOutput().length, 0, "expected 0 message"); 89 host.checkTimeoutQueueLengthAndRun(1); 90 // Completed event since file is missing 91 assert.equal(host.getOutput().length, 1, "expect 1 message"); 92 verifyRequestCompleted(session.getSeq(), 0); 93 } 94 { 95 const getErrId = session.getNextSeq(); 96 // send geterr for a valid file 97 session.executeCommandSeq(<protocol.GeterrRequest>{ 98 command: "geterr", 99 arguments: { files: [f1.path] } 100 }); 101 102 assert.equal(host.getOutput().length, 0, "expect 0 messages"); 103 104 // run new request 105 session.executeCommandSeq(<protocol.ProjectInfoRequest>{ 106 command: "projectInfo", 107 arguments: { file: f1.path } 108 }); 109 session.clearMessages(); 110 111 // cancel previously issued Geterr 112 cancellationToken.setRequestToCancel(getErrId); 113 host.runQueuedTimeoutCallbacks(); 114 115 assert.equal(host.getOutput().length, 1, "expect 1 message"); 116 verifyRequestCompleted(getErrId, 0); 117 118 cancellationToken.resetToken(); 119 } 120 { 121 const getErrId = session.getNextSeq(); 122 session.executeCommandSeq(<protocol.GeterrRequest>{ 123 command: "geterr", 124 arguments: { files: [f1.path] } 125 }); 126 assert.equal(host.getOutput().length, 0, "expect 0 messages"); 127 128 // run first step 129 host.runQueuedTimeoutCallbacks(); 130 assert.equal(host.getOutput().length, 1, "expect 1 message"); 131 const e1 = <protocol.Event>getMessage(0); 132 assert.equal(e1.event, "syntaxDiag"); 133 session.clearMessages(); 134 135 cancellationToken.setRequestToCancel(getErrId); 136 host.runQueuedImmediateCallbacks(); 137 assert.equal(host.getOutput().length, 1, "expect 1 message"); 138 verifyRequestCompleted(getErrId, 0); 139 140 cancellationToken.resetToken(); 141 } 142 { 143 const getErrId = session.getNextSeq(); 144 session.executeCommandSeq(<protocol.GeterrRequest>{ 145 command: "geterr", 146 arguments: { files: [f1.path] } 147 }); 148 assert.equal(host.getOutput().length, 0, "expect 0 messages"); 149 150 // run first step 151 host.runQueuedTimeoutCallbacks(); 152 assert.equal(host.getOutput().length, 1, "expect 1 message"); 153 const e1 = <protocol.Event>getMessage(0); 154 assert.equal(e1.event, "syntaxDiag"); 155 session.clearMessages(); 156 157 // the semanticDiag message 158 host.runQueuedImmediateCallbacks(); 159 assert.equal(host.getOutput().length, 1); 160 const e2 = <protocol.Event>getMessage(0); 161 assert.equal(e2.event, "semanticDiag"); 162 session.clearMessages(); 163 164 host.runQueuedImmediateCallbacks(1); 165 assert.equal(host.getOutput().length, 2); 166 const e3 = <protocol.Event>getMessage(0); 167 assert.equal(e3.event, "suggestionDiag"); 168 verifyRequestCompleted(getErrId, 1); 169 170 cancellationToken.resetToken(); 171 } 172 { 173 const getErr1 = session.getNextSeq(); 174 session.executeCommandSeq(<protocol.GeterrRequest>{ 175 command: "geterr", 176 arguments: { files: [f1.path] } 177 }); 178 assert.equal(host.getOutput().length, 0, "expect 0 messages"); 179 // run first step 180 host.runQueuedTimeoutCallbacks(); 181 assert.equal(host.getOutput().length, 1, "expect 1 message"); 182 const e1 = <protocol.Event>getMessage(0); 183 assert.equal(e1.event, "syntaxDiag"); 184 session.clearMessages(); 185 186 session.executeCommandSeq(<protocol.GeterrRequest>{ 187 command: "geterr", 188 arguments: { files: [f1.path] } 189 }); 190 // make sure that getErr1 is completed 191 verifyRequestCompleted(getErr1, 0); 192 } 193 194 function verifyRequestCompleted(expectedSeq: number, n: number) { 195 const event = <protocol.RequestCompletedEvent>getMessage(n); 196 assert.equal(event.event, "requestCompleted"); 197 assert.equal(event.body.request_seq, expectedSeq, "expectedSeq"); 198 session.clearMessages(); 199 } 200 201 function getMessage(n: number) { 202 return JSON.parse(server.extractMessage(host.getOutput()[n])); 203 } 204 }); 205 206 it("Lower priority tasks are cancellable", () => { 207 const f1 = { 208 path: "/a/app.ts", 209 content: `{ let x = 1; } var foo = "foo"; var bar = "bar"; var fooBar = "fooBar";` 210 }; 211 const config = { 212 path: "/a/tsconfig.json", 213 content: JSON.stringify({ 214 compilerOptions: {} 215 }) 216 }; 217 const cancellationToken = new TestServerCancellationToken(/*cancelAfterRequest*/ 3); 218 const host = createServerHost([f1, config]); 219 const session = createSession(host, { 220 canUseEvents: true, 221 eventHandler: noop, 222 cancellationToken, 223 throttleWaitMilliseconds: 0 224 }); 225 { 226 session.executeCommandSeq(<protocol.OpenRequest>{ 227 command: "open", 228 arguments: { file: f1.path } 229 }); 230 231 // send navbar request (normal priority) 232 session.executeCommandSeq(<protocol.NavBarRequest>{ 233 command: "navbar", 234 arguments: { file: f1.path } 235 }); 236 237 // ensure the nav bar request can be canceled 238 verifyExecuteCommandSeqIsCancellable(<protocol.NavBarRequest>{ 239 command: "navbar", 240 arguments: { file: f1.path } 241 }); 242 243 // send outlining spans request (normal priority) 244 session.executeCommandSeq(<protocol.OutliningSpansRequestFull>{ 245 command: "outliningSpans", 246 arguments: { file: f1.path } 247 }); 248 249 // ensure the outlining spans request can be canceled 250 verifyExecuteCommandSeqIsCancellable(<protocol.OutliningSpansRequestFull>{ 251 command: "outliningSpans", 252 arguments: { file: f1.path } 253 }); 254 } 255 256 function verifyExecuteCommandSeqIsCancellable<T extends server.protocol.Request>(request: Partial<T>) { 257 // Set the next request to be cancellable 258 // The cancellation token will cancel the request the third time 259 // isCancellationRequested() is called. 260 cancellationToken.setRequestToCancel(session.getNextSeq()); 261 let operationCanceledExceptionThrown = false; 262 263 try { 264 session.executeCommandSeq(request); 265 } 266 catch (e) { 267 assert(e instanceof OperationCanceledException); 268 operationCanceledExceptionThrown = true; 269 } 270 assert(operationCanceledExceptionThrown, "Operation Canceled Exception not thrown for request: " + JSON.stringify(request)); 271 } 272 }); 273 }); 274} 275