Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions crates/registry-render/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -621,8 +621,13 @@ mod tests {
framing_stand_in(dir, &body, after)
}

/// A stand-in worker that writes `body` as one frame and then runs
/// `after` (shell).
/// A stand-in worker that reads the whole request frame, writes `body` as
/// one frame and then runs `after` (shell). A real worker reads its request
/// before it answers; one that answers and exits first would make the
/// parent's request write fail with a broken pipe. The parent keeps stdin
/// open until it has the answer, so the request is read by its length
/// prefix (little-endian `u32`), one byte per `dd` read so that nothing
/// past the frame is consumed.
fn framing_stand_in(dir: &std::path::Path, body: &[u8], after: &str) -> std::path::PathBuf {
let mut frame = (body.len() as u32).to_le_bytes().to_vec();
frame.extend_from_slice(body);
Expand All @@ -631,7 +636,14 @@ mod tests {
let stand_in = dir.join("answering-worker");
std::fs::write(
&stand_in,
format!("#!/bin/sh\ncat '{}'\n{after}\n", frame_file.display()),
format!(
"#!/bin/sh\n\
set -- $(dd bs=1 count=4 2>/dev/null | od -An -tu1)\n\
dd bs=1 count=$(($1 + $2 * 256 + $3 * 65536 + $4 * 16777216)) \
of=/dev/null 2>/dev/null\n\
cat '{}'\n{after}\n",
frame_file.display()
),
)
.expect("write stand-in");
use std::os::unix::fs::PermissionsExt;
Expand Down
39 changes: 37 additions & 2 deletions editors/vscode/test/trusted.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ suite('Registry Stack extension', () => {

const alphaFolder = vscode.workspace.workspaceFolders?.find((folder) => folder.name === 'alpha');
assert.ok(alphaFolder, 'alpha workspace folder is available');
fs.writeFileSync(
await writeWatchedFile(
path.join(alphaFolder.uri.fsPath, 'registry.yaml'),
'apiVersion: id.registrystack.org/formats/breg/project/v1alpha1\nkind: BRegProject\nproject: { id: alpha-reloaded }\n',
);
Expand Down Expand Up @@ -271,7 +271,7 @@ suite('Registry Stack extension', () => {
await vscode.workspace.openTextDocument(retiredQuestionPath);
await assertLanguageServerStartCountStable(startLog, 4);

assert.strictEqual(vscode.workspace.updateWorkspaceFolders(3, 3), true);
await changeWorkspaceFolders(3, 3);
await assertWorkspaceFolderCount(3);

// Deleting the installer metadata and restarting proves the PATH
Expand Down Expand Up @@ -335,6 +335,9 @@ async function assertWorkspaceFolderCount(expected: number): Promise<void> {
// VS Code requires the workspace-change event to finish before another
// updateWorkspaceFolders call, even when workspaceFolders already has its
// next value. Rapid product add/remove cycles must await that event.
// A workspace-scoped configuration update must also wait: it edits the same
// .code-workspace file, and VS Code refuses it while the folder change is
// still unsaved there.
async function changeWorkspaceFolders(
start: number,
deleteCount: number,
Expand All @@ -352,6 +355,38 @@ async function changeWorkspaceFolders(
}
}

// An edit made outside the editor reaches the language server only as a file
// event from VS Code's watcher, which does not guarantee an event for every
// write. Rewrite the file until the extension host reports it, so a later
// symbol assertion fails only when a delivered change was not indexed. A
// string glob adds no watch request of its own and sees the same events as the
// language client's watchers.
async function writeWatchedFile(filePath: string, content: string): Promise<void> {
const watcher = vscode.workspace.createFileSystemWatcher(`**/${path.basename(filePath)}`);
let reported = false;
const onEvent = (uri: vscode.Uri) => {
if (uri.fsPath === filePath) {
reported = true;
}
};
watcher.onDidCreate(onEvent);
watcher.onDidChange(onEvent);
try {
for (let write = 0; write < 3; write += 1) {
fs.writeFileSync(filePath, content);
for (let attempt = 0; attempt < 20; attempt += 1) {
if (reported) {
return;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
assert.fail(`VS Code reported no file event for ${filePath}`);
} finally {
watcher.dispose();
}
}

async function assertWorkspaceSymbol(expected: string, documentPath?: string): Promise<void> {
const expectedPath = documentPath === undefined ? undefined : fs.realpathSync(documentPath);
for (let attempt = 0; attempt < 50; attempt += 1) {
Expand Down
Loading