fix(watcher): cap startup wait
This commit is contained in:
@@ -1,8 +1,3 @@
|
||||
// Watcher Integration Test — proves the FS-watcher → LSP wiring works
|
||||
// end-to-end against gopls: a stale "undefined symbol" diagnostic clears
|
||||
// after the missing file is created externally (no LSP query touches it).
|
||||
//
|
||||
// This is the canonical staleness scenario from `_scratch/plan-fs-watching.md`.
|
||||
import { describe, it, before, after } from "node:test";
|
||||
import * as assert from "node:assert/strict";
|
||||
import * as fs from "node:fs";
|
||||
@@ -17,10 +12,6 @@ import {
|
||||
|
||||
const skip = requireServer("gopls");
|
||||
|
||||
// Poll Until - Runs `fn` repeatedly until `predicate(result)` is true or
|
||||
// the timeout elapses. We need this because gopls reanalysis after a
|
||||
// workspace/didChangeWatchedFiles is asynchronous; diagnostics arrive on
|
||||
// a `textDocument/publishDiagnostics` push that we then re-fetch.
|
||||
async function pollUntil<T>(
|
||||
fn: () => Promise<T>,
|
||||
predicate: (v: T) => boolean,
|
||||
@@ -53,12 +44,9 @@ describe("watcher: gopls picks up externally-created files", { skip: skip ?? und
|
||||
await stopTestDaemon(env);
|
||||
|
||||
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-lsp-gopls-watch-"));
|
||||
// Minimal Go Module - go.mod is gopls's primary root marker.
|
||||
fs.writeFileSync(path.join(tmpDir, "go.mod"), "module example.com/wtest\n\ngo 1.21\n");
|
||||
mainFile = path.join(tmpDir, "main.go");
|
||||
helperFile = path.join(tmpDir, "helper.go");
|
||||
// main.go references Helper() which doesn't exist yet \u2014 should produce
|
||||
// an "undefined: Helper" diagnostic.
|
||||
fs.writeFileSync(
|
||||
mainFile,
|
||||
"package main\n\nfunc main() {\n\tHelper()\n}\n",
|
||||
@@ -72,9 +60,6 @@ describe("watcher: gopls picks up externally-created files", { skip: skip ?? und
|
||||
});
|
||||
|
||||
it("initially reports undefined symbol", async () => {
|
||||
// Poll - gopls's workspace load on a fresh tmp dir takes a beat; the
|
||||
// first diagnostics call can return "No active builds contain ..."
|
||||
// before the real analysis lands. Wait up to 15s for it to settle.
|
||||
const result = await pollUntil(
|
||||
async () =>
|
||||
(await runCliJson(
|
||||
@@ -103,16 +88,11 @@ describe("watcher: gopls picks up externally-created files", { skip: skip ?? und
|
||||
});
|
||||
|
||||
it("clears the diagnostic after helper.go is created externally", async () => {
|
||||
// Create The Missing File Without Touching It Via LSP - This is the
|
||||
// whole point: only chokidar + workspace/didChangeWatchedFiles can tell
|
||||
// gopls about helper.go's existence.
|
||||
fs.writeFileSync(
|
||||
helperFile,
|
||||
"package main\n\nfunc Helper() {}\n",
|
||||
);
|
||||
|
||||
// Poll - Allow up to 15s for the watcher to fire, gopls to reanalyze,
|
||||
// and the diagnostic to clear. Re-query the same file (main.go) only.
|
||||
const result = await pollUntil(
|
||||
async () =>
|
||||
(await runCliJson(
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// Watcher Unit Tests — exercises WorkspaceWatcher against a temp dir with
|
||||
// real chokidar. We use real FS because mocking it would test the mock,
|
||||
// not the actual behavior under inotify.
|
||||
import { describe, it, before, after, beforeEach } from "node:test";
|
||||
import * as assert from "node:assert/strict";
|
||||
import * as fs from "node:fs";
|
||||
@@ -9,9 +6,6 @@ import * as path from "node:path";
|
||||
import { WorkspaceWatcher, type FileEvent } from "../../src/watcher.ts";
|
||||
import { pathToUri } from "../../src/root.ts";
|
||||
|
||||
// Wait For - Polls a predicate up to timeoutMs. Returns true if it became
|
||||
// true, false if it timed out. Used to wait on async chokidar events
|
||||
// without arbitrary sleeps.
|
||||
async function waitFor(
|
||||
predicate: () => boolean,
|
||||
timeoutMs = 2000,
|
||||
@@ -24,8 +18,6 @@ async function waitFor(
|
||||
return predicate();
|
||||
}
|
||||
|
||||
// Wait Quiet - Waits for the debounce window to elapse so any pending
|
||||
// events flush. Slightly longer than DEBOUNCE_MAX_WAIT_MS.
|
||||
const FLUSH_WAIT_MS = 700;
|
||||
|
||||
describe("WorkspaceWatcher", () => {
|
||||
@@ -47,7 +39,6 @@ describe("WorkspaceWatcher", () => {
|
||||
watcher = null;
|
||||
}
|
||||
received = [];
|
||||
// Reset Dir - Wipe contents between tests so file lists are predictable.
|
||||
for (const entry of fs.readdirSync(tmpDir)) {
|
||||
fs.rmSync(path.join(tmpDir, entry), { recursive: true, force: true });
|
||||
}
|
||||
@@ -140,7 +131,6 @@ describe("WorkspaceWatcher", () => {
|
||||
|
||||
it("respects WatchKind to filter event types", async () => {
|
||||
watcher = new WorkspaceWatcher(tmpDir, (evs) => received.push(evs));
|
||||
// Kind 1 = Create only - delete events should be suppressed.
|
||||
watcher.setPatterns([{ globPattern: "**/*.ts", kind: 1 }]);
|
||||
await watcher.ready();
|
||||
|
||||
@@ -166,8 +156,6 @@ describe("WorkspaceWatcher", () => {
|
||||
watcher.setPatterns([{ globPattern: "**/*.ts" }]);
|
||||
await watcher.ready();
|
||||
|
||||
// Write Several Files Quickly - within the 50ms debounce window they
|
||||
// should all land in one batch, capped at 500ms max wait.
|
||||
for (let i = 0; i < 5; i++) {
|
||||
fs.writeFileSync(path.join(tmpDir, `f${i}.ts`), "x");
|
||||
}
|
||||
@@ -176,8 +164,6 @@ describe("WorkspaceWatcher", () => {
|
||||
|
||||
const all = received.flat();
|
||||
assert.strictEqual(all.length, 5, `Expected 5 events, got ${all.length}`);
|
||||
// The batches should be << 5 (debounce coalesces). Allow up to 2 in
|
||||
// case the loop straddled a flush boundary.
|
||||
assert.ok(
|
||||
received.length <= 2,
|
||||
`Expected <=2 batches, got ${received.length}: ${JSON.stringify(received)}`,
|
||||
@@ -217,8 +203,6 @@ describe("WorkspaceWatcher", () => {
|
||||
});
|
||||
|
||||
it("matches absolute-path glob patterns (gopls-style)", async () => {
|
||||
// Regression - gopls registers e.g. `/tmp/.../**/*.{go,mod}` rather
|
||||
// than a bare relative `**/*.go`. The matcher must accept both.
|
||||
watcher = new WorkspaceWatcher(tmpDir, (evs) => received.push(evs));
|
||||
watcher.setPatterns([
|
||||
{ globPattern: `${tmpDir}/**/*.{go,mod}` },
|
||||
@@ -240,7 +224,6 @@ describe("WorkspaceWatcher", () => {
|
||||
watcher.setPatterns([{ globPattern: "**/*.ts" }]);
|
||||
await watcher.ready();
|
||||
|
||||
// Create Then Delete - both within the debounce quiet window.
|
||||
const file = path.join(tmpDir, "transient.ts");
|
||||
fs.writeFileSync(file, "x");
|
||||
fs.unlinkSync(file);
|
||||
@@ -255,15 +238,12 @@ describe("WorkspaceWatcher", () => {
|
||||
});
|
||||
|
||||
it("coalesces Deleted+Created (replacement) to Changed", async () => {
|
||||
// Setup - file exists before the watcher starts, so chokidar's initial
|
||||
// scan registers it (with ignoreInitial: true, no event fires).
|
||||
const file = path.join(tmpDir, "replace.ts");
|
||||
fs.writeFileSync(file, "v1");
|
||||
watcher = new WorkspaceWatcher(tmpDir, (evs) => received.push(evs));
|
||||
watcher.setPatterns([{ globPattern: "**/*.ts" }]);
|
||||
await watcher.ready();
|
||||
|
||||
// Delete Then Recreate - within one debounce window.
|
||||
fs.unlinkSync(file);
|
||||
fs.writeFileSync(file, "v2");
|
||||
|
||||
@@ -272,11 +252,6 @@ describe("WorkspaceWatcher", () => {
|
||||
const all = received.flat();
|
||||
const events = all.filter((e) => e.uri === pathToUri(file));
|
||||
assert.ok(events.length > 0, `Expected an event for replaced file`);
|
||||
// Expected: the coalesced single event is Changed (type=2). Some
|
||||
// platforms may split the delete+create across debounce windows, in
|
||||
// which case the server still sees correct state. Accept either:
|
||||
// - one Changed event (coalesced), or
|
||||
// - Deleted then Created in separate batches (not coalesced, fine)
|
||||
const types = events.map((e) => e.type).sort();
|
||||
const acceptable =
|
||||
JSON.stringify(types) === JSON.stringify([2]) ||
|
||||
@@ -293,8 +268,6 @@ describe("WorkspaceWatcher", () => {
|
||||
await watcher.ready();
|
||||
|
||||
fs.writeFileSync(path.join(tmpDir, "pending.ts"), "x");
|
||||
// Don't wait for the debounce - clear patterns immediately so the
|
||||
// pending batch should be dropped, not flushed.
|
||||
await new Promise((r) => setTimeout(r, 10));
|
||||
watcher.setPatterns([]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user