1namespace ts.tscWatch { 2 import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory; 3 describe("unittests:: tsc-watch:: watchEnvironment:: tsc-watch with different polling/non polling options", () => { 4 const scenario = "watchEnvironment"; 5 verifyTscWatch({ 6 scenario, 7 subScenario: "watchFile/using dynamic priority polling", 8 commandLineArgs: ["--w", `/a/username/project/typescript.ts`], 9 sys: () => { 10 const projectFolder = "/a/username/project"; 11 const file1: File = { 12 path: `${projectFolder}/typescript.ts`, 13 content: "var z = 10;" 14 }; 15 const environmentVariables = new Map<string, string>(); 16 environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling); 17 return createWatchedSystem([file1, libFile], { environmentVariables }); 18 }, 19 changes: [ 20 { 21 caption: "Time spent to Transition libFile and file1 to low priority queue", 22 change: noop, 23 timeouts: (sys, programs) => { 24 const initialProgram = programs[0][0]; 25 const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; 26 for (let index = 0; index < mediumPollingIntervalThreshold; index++) { 27 // Transition libFile and file1 to low priority queue 28 sys.checkTimeoutQueueLengthAndRun(1); 29 assert.deepEqual(programs[0][0], initialProgram); 30 } 31 return; 32 }, 33 }, 34 { 35 caption: "Make change to file", 36 // Make a change to file 37 change: sys => sys.writeFile("/a/username/project/typescript.ts", "var zz30 = 100;"), 38 // During this timeout the file would be detected as unchanged 39 timeouts: checkSingleTimeoutQueueLengthAndRun, 40 }, 41 { 42 caption: "Callbacks: medium priority + high priority queue and scheduled program update", 43 change: noop, 44 // Callbacks: medium priority + high priority queue and scheduled program update 45 // This should detect change in the file 46 timeouts: sys => sys.checkTimeoutQueueLengthAndRun(3), 47 }, 48 { 49 caption: "Polling queues polled and everything is in the high polling queue", 50 change: noop, 51 timeouts: (sys, programs) => { 52 const initialProgram = programs[0][0]; 53 const mediumPollingIntervalThreshold = unchangedPollThresholds[PollingInterval.Medium]; 54 const newThreshold = unchangedPollThresholds[PollingInterval.Low] + mediumPollingIntervalThreshold; 55 for (let fileUnchangeDetected = 1; fileUnchangeDetected < newThreshold; fileUnchangeDetected++) { 56 // For high + Medium/low polling interval 57 sys.checkTimeoutQueueLengthAndRun(2); 58 assert.deepEqual(programs[0][0], initialProgram); 59 } 60 61 // Everything goes in high polling interval queue 62 sys.checkTimeoutQueueLengthAndRun(1); 63 return; 64 }, 65 } 66 ] 67 }); 68 69 describe("tsc-watch when watchDirectories implementation", () => { 70 function verifyRenamingFileInSubFolder(subScenario: string, tscWatchDirectory: Tsc_WatchDirectory) { 71 const projectFolder = "/a/username/project"; 72 const projectSrcFolder = `${projectFolder}/src`; 73 const configFile: File = { 74 path: `${projectFolder}/tsconfig.json`, 75 content: JSON.stringify({ 76 watchOptions: { 77 synchronousWatchDirectory: true 78 } 79 }) 80 }; 81 const file: File = { 82 path: `${projectSrcFolder}/file1.ts`, 83 content: "" 84 }; 85 verifyTscWatch({ 86 scenario, 87 subScenario: `watchDirectories/${subScenario}`, 88 commandLineArgs: ["--w", "-p", configFile.path], 89 sys: () => { 90 const files = [file, configFile, libFile]; 91 const environmentVariables = new Map<string, string>(); 92 environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); 93 return createWatchedSystem(files, { environmentVariables }); 94 }, 95 changes: [ 96 { 97 caption: "Rename file1 to file2", 98 // Rename the file: 99 change: sys => sys.renameFile(file.path, file.path.replace("file1.ts", "file2.ts")), 100 timeouts: sys => { 101 if (tscWatchDirectory === Tsc_WatchDirectory.DynamicPolling) { 102 // With dynamic polling the fs change would be detected only by running timeouts 103 sys.runQueuedTimeoutCallbacks(); 104 } 105 // Delayed update program 106 sys.runQueuedTimeoutCallbacks(); 107 return; 108 }, 109 }, 110 ], 111 }); 112 } 113 114 verifyRenamingFileInSubFolder("uses watchFile when renaming file in subfolder", Tsc_WatchDirectory.WatchFile); 115 116 verifyRenamingFileInSubFolder("uses non recursive watchDirectory when renaming file in subfolder", Tsc_WatchDirectory.NonRecursiveWatchDirectory); 117 118 verifyRenamingFileInSubFolder("uses non recursive dynamic polling when renaming file in subfolder", Tsc_WatchDirectory.DynamicPolling); 119 120 verifyTscWatch({ 121 scenario, 122 subScenario: "watchDirectories/when there are symlinks to folders in recursive folders", 123 commandLineArgs: ["--w"], 124 sys: () => { 125 const cwd = "/home/user/projects/myproject"; 126 const file1: File = { 127 path: `${cwd}/src/file.ts`, 128 content: `import * as a from "a"` 129 }; 130 const tsconfig: File = { 131 path: `${cwd}/tsconfig.json`, 132 content: `{ "compilerOptions": { "extendedDiagnostics": true, "traceResolution": true }}` 133 }; 134 const realA: File = { 135 path: `${cwd}/node_modules/reala/index.d.ts`, 136 content: `export {}` 137 }; 138 const realB: File = { 139 path: `${cwd}/node_modules/realb/index.d.ts`, 140 content: `export {}` 141 }; 142 const symLinkA: SymLink = { 143 path: `${cwd}/node_modules/a`, 144 symLink: `${cwd}/node_modules/reala` 145 }; 146 const symLinkB: SymLink = { 147 path: `${cwd}/node_modules/b`, 148 symLink: `${cwd}/node_modules/realb` 149 }; 150 const symLinkBInA: SymLink = { 151 path: `${cwd}/node_modules/reala/node_modules/b`, 152 symLink: `${cwd}/node_modules/b` 153 }; 154 const symLinkAInB: SymLink = { 155 path: `${cwd}/node_modules/realb/node_modules/a`, 156 symLink: `${cwd}/node_modules/a` 157 }; 158 const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; 159 const environmentVariables = new Map<string, string>(); 160 environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); 161 return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); 162 }, 163 changes: emptyArray 164 }); 165 166 verifyTscWatch({ 167 scenario, 168 subScenario: "watchDirectories/with non synchronous watch directory", 169 commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], 170 sys: () => { 171 const configFile: File = { 172 path: `${projectRoot}/tsconfig.json`, 173 content: "{}" 174 }; 175 const file1: File = { 176 path: `${projectRoot}/src/file1.ts`, 177 content: `import { x } from "file2";` 178 }; 179 const file2: File = { 180 path: `${projectRoot}/node_modules/file2/index.d.ts`, 181 content: `export const x = 10;` 182 }; 183 const files = [libFile, file1, file2, configFile]; 184 return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); 185 }, 186 changes: [ 187 { 188 caption: "Directory watch updates because of file1.js creation", 189 change: noop, 190 timeouts: sys => { 191 sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for file1.js output 192 sys.checkTimeoutQueueLength(0); 193 }, 194 }, 195 { 196 caption: "Remove directory node_modules", 197 // Remove directory node_modules 198 change: sys => sys.deleteFolder(`${projectRoot}/node_modules`, /*recursive*/ true), 199 timeouts: sys => { 200 sys.checkTimeoutQueueLength(3); // 1. Failed lookup invalidation 2. For updating program and 3. for updating child watches 201 sys.runQueuedTimeoutCallbacks(sys.getNextTimeoutId() - 2); // Update program 202 }, 203 }, 204 { 205 caption: "Pending directory watchers and program update", 206 change: noop, 207 timeouts: sys => { 208 sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers 209 sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update 210 sys.checkTimeoutQueueLengthAndRun(1); // Actual program update 211 sys.checkTimeoutQueueLength(0); 212 }, 213 }, 214 { 215 caption: "Start npm install", 216 // npm install 217 change: sys => sys.createDirectory(`${projectRoot}/node_modules`), 218 timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure 219 }, 220 { 221 caption: "npm install folder creation of file2", 222 change: sys => sys.createDirectory(`${projectRoot}/node_modules/file2`), 223 timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure 224 }, 225 { 226 caption: "npm install index file in file2", 227 change: sys => sys.writeFile(`${projectRoot}/node_modules/file2/index.d.ts`, `export const x = 10;`), 228 timeouts: sys => sys.checkTimeoutQueueLength(1), // To update folder structure 229 }, 230 { 231 caption: "Updates the program", 232 change: noop, 233 timeouts: sys => { 234 sys.runQueuedTimeoutCallbacks(); 235 sys.checkTimeoutQueueLength(2); // To Update program and failed lookup update 236 }, 237 }, 238 { 239 caption: "Invalidates module resolution cache", 240 change: noop, 241 timeouts: sys => { 242 sys.runQueuedTimeoutCallbacks(); 243 sys.checkTimeoutQueueLength(1); // To Update program 244 }, 245 }, 246 { 247 caption: "Pending updates", 248 change: noop, 249 timeouts: sys => { 250 sys.runQueuedTimeoutCallbacks(); 251 sys.checkTimeoutQueueLength(0); 252 }, 253 }, 254 ], 255 }); 256 257 verifyTscWatch({ 258 scenario, 259 subScenario: "watchDirectories/with non synchronous watch directory with outDir and declaration enabled", 260 commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], 261 sys: () => { 262 const configFile: File = { 263 path: `${projectRoot}/tsconfig.json`, 264 content: JSON.stringify({ compilerOptions: { outDir: "dist", declaration: true } }) 265 }; 266 const file1: File = { 267 path: `${projectRoot}/src/file1.ts`, 268 content: `import { x } from "file2";` 269 }; 270 const file2: File = { 271 path: `${projectRoot}/node_modules/file2/index.d.ts`, 272 content: `export const x = 10;` 273 }; 274 const files = [libFile, file1, file2, configFile]; 275 return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); 276 }, 277 changes: [ 278 noopChange, 279 { 280 caption: "Add new file, should schedule and run timeout to update directory watcher", 281 change: sys => sys.writeFile(`${projectRoot}/src/file3.ts`, `export const y = 10;`), 282 timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch 283 }, 284 { 285 caption: "Actual program update to include new file", 286 change: noop, 287 timeouts: sys => sys.checkTimeoutQueueLengthAndRun(2), // Scheduling failed lookup update and program update 288 }, 289 { 290 caption: "After program emit with new file, should schedule and run timeout to update directory watcher", 291 change: noop, 292 timeouts: checkSingleTimeoutQueueLengthAndRun, // Update the child watch 293 }, 294 noopChange, 295 ], 296 }); 297 298 verifyTscWatch({ 299 scenario, 300 subScenario: "watchDirectories/with non synchronous watch directory renaming a file", 301 commandLineArgs: ["--w", "-p", `${projectRoot}/tsconfig.json`], 302 sys: () => { 303 const configFile: File = { 304 path: `${projectRoot}/tsconfig.json`, 305 content: JSON.stringify({ compilerOptions: { outDir: "dist" } }) 306 }; 307 const file1: File = { 308 path: `${projectRoot}/src/file1.ts`, 309 content: `import { x } from "./file2";` 310 }; 311 const file2: File = { 312 path: `${projectRoot}/src/file2.ts`, 313 content: `export const x = 10;` 314 }; 315 const files = [libFile, file1, file2, configFile]; 316 return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); 317 }, 318 changes: [ 319 noopChange, 320 { 321 caption: "rename the file", 322 change: sys => sys.renameFile(`${projectRoot}/src/file2.ts`, `${projectRoot}/src/renamed.ts`), 323 timeouts: sys => { 324 sys.checkTimeoutQueueLength(2); // 1. For updating program and 2. for updating child watches 325 sys.runQueuedTimeoutCallbacks(1); // Update program 326 }, 327 }, 328 { 329 caption: "Pending directory watchers and program update", 330 change: noop, 331 timeouts: sys => { 332 sys.checkTimeoutQueueLengthAndRun(1); // To update directory watchers 333 sys.checkTimeoutQueueLengthAndRun(2); // To Update program and failed lookup update 334 sys.checkTimeoutQueueLengthAndRun(1); // Actual program update 335 sys.checkTimeoutQueueLength(0); 336 }, 337 }, 338 ], 339 }); 340 }); 341 342 describe("handles watch compiler options", () => { 343 verifyTscWatch({ 344 scenario, 345 subScenario: "watchOptions/with watchFile option", 346 commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], 347 sys: () => { 348 const configFile: File = { 349 path: "/a/b/tsconfig.json", 350 content: JSON.stringify({ 351 watchOptions: { 352 watchFile: "UseFsEvents" 353 } 354 }) 355 }; 356 const files = [libFile, commonFile1, commonFile2, configFile]; 357 return createWatchedSystem(files); 358 }, 359 changes: emptyArray 360 }); 361 362 verifyTscWatch({ 363 scenario, 364 subScenario: "watchOptions/with watchDirectory option", 365 commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], 366 sys: () => { 367 const configFile: File = { 368 path: "/a/b/tsconfig.json", 369 content: JSON.stringify({ 370 watchOptions: { 371 watchDirectory: "UseFsEvents" 372 } 373 }) 374 }; 375 const files = [libFile, commonFile1, commonFile2, configFile]; 376 return createWatchedSystem(files, { runWithoutRecursiveWatches: true }); 377 }, 378 changes: emptyArray 379 }); 380 381 verifyTscWatch({ 382 scenario, 383 subScenario: "watchOptions/with fallbackPolling option", 384 commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json"], 385 sys: () => { 386 const configFile: File = { 387 path: "/a/b/tsconfig.json", 388 content: JSON.stringify({ 389 watchOptions: { 390 fallbackPolling: "PriorityInterval" 391 } 392 }) 393 }; 394 const files = [libFile, commonFile1, commonFile2, configFile]; 395 return createWatchedSystem(files, { runWithoutRecursiveWatches: true, runWithFallbackPolling: true }); 396 }, 397 changes: emptyArray 398 }); 399 400 verifyTscWatch({ 401 scenario, 402 subScenario: "watchOptions/with watchFile as watch options to extend", 403 commandLineArgs: ["-w", "-p", "/a/b/tsconfig.json", "--watchFile", "UseFsEvents"], 404 sys: () => { 405 const configFile: File = { 406 path: "/a/b/tsconfig.json", 407 content: "{}" 408 }; 409 const files = [libFile, commonFile1, commonFile2, configFile]; 410 return createWatchedSystem(files); 411 }, 412 changes: emptyArray 413 }); 414 415 describe("exclude options", () => { 416 function sys(watchOptions: WatchOptions, runWithoutRecursiveWatches?: boolean): WatchedSystem { 417 const configFile: File = { 418 path: `${projectRoot}/tsconfig.json`, 419 content: JSON.stringify({ exclude: ["node_modules"], watchOptions }) 420 }; 421 const main: File = { 422 path: `${projectRoot}/src/main.ts`, 423 content: `import { foo } from "bar"; foo();` 424 }; 425 const bar: File = { 426 path: `${projectRoot}/node_modules/bar/index.d.ts`, 427 content: `export { foo } from "./foo";` 428 }; 429 const foo: File = { 430 path: `${projectRoot}/node_modules/bar/foo.d.ts`, 431 content: `export function foo(): string;` 432 }; 433 const fooBar: File = { 434 path: `${projectRoot}/node_modules/bar/fooBar.d.ts`, 435 content: `export function fooBar(): string;` 436 }; 437 const temp: File = { 438 path: `${projectRoot}/node_modules/bar/temp/index.d.ts`, 439 content: "export function temp(): string;" 440 }; 441 const files = [libFile, main, bar, foo, fooBar, temp, configFile]; 442 return createWatchedSystem(files, { currentDirectory: projectRoot, runWithoutRecursiveWatches }); 443 } 444 445 function verifyWorker(...additionalFlags: string[]) { 446 verifyTscWatch({ 447 scenario, 448 subScenario: `watchOptions/with excludeFiles option${additionalFlags.join("")}`, 449 commandLineArgs: ["-w", ...additionalFlags], 450 sys: () => sys({ excludeFiles: ["node_modules/*"] }), 451 changes: [ 452 { 453 caption: "Change foo", 454 change: sys => replaceFileText(sys, `${projectRoot}/node_modules/bar/foo.d.ts`, "foo", "fooBar"), 455 timeouts: sys => sys.checkTimeoutQueueLength(0), 456 } 457 ] 458 }); 459 460 verifyTscWatch({ 461 scenario, 462 subScenario: `watchOptions/with excludeDirectories option${additionalFlags.join("")}`, 463 commandLineArgs: ["-w", ...additionalFlags], 464 sys: () => sys({ excludeDirectories: ["node_modules"] }), 465 changes: [ 466 { 467 caption: "delete fooBar", 468 change: sys => sys.deleteFile(`${projectRoot}/node_modules/bar/fooBar.d.ts`), 469 timeouts: sys => sys.checkTimeoutQueueLength(0), } 470 ] 471 }); 472 473 verifyTscWatch({ 474 scenario, 475 subScenario: `watchOptions/with excludeDirectories option with recursive directory watching${additionalFlags.join("")}`, 476 commandLineArgs: ["-w", ...additionalFlags], 477 sys: () => sys({ excludeDirectories: ["**/temp"] }, /*runWithoutRecursiveWatches*/ true), 478 changes: [ 479 { 480 caption: "Directory watch updates because of main.js creation", 481 change: noop, 482 timeouts: sys => { 483 sys.checkTimeoutQueueLengthAndRun(1); // To update directory callbacks for main.js output 484 sys.checkTimeoutQueueLength(0); 485 }, 486 }, 487 { 488 caption: "add new folder to temp", 489 change: sys => sys.ensureFileOrFolder({ path: `${projectRoot}/node_modules/bar/temp/fooBar/index.d.ts`, content: "export function temp(): string;" }), 490 timeouts: sys => sys.checkTimeoutQueueLength(0), 491 } 492 ] 493 }); 494 } 495 496 verifyWorker(); 497 verifyWorker("-extendedDiagnostics"); 498 }); 499 }); 500 }); 501} 502