From 0d82b00f8378601a42506aa092cad16b86203edb Mon Sep 17 00:00:00 2001 From: cmaranes Date: Thu, 22 Oct 2026 16:32:40 +0200 Subject: [PATCH 1/3] ci: bump actions to their Node 24 majors and build on Node 22 checkout v7, setup-node v7, setup-python v7, cache v6, upload-artifact v7, download-artifact v8 (fails on digest mismatch). Removes the Node 20 action-runtime deprecation warnings; Node 20 itself is end-of-life. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JBDmV8VxTwVxxpuC8NMG8y --- .github/workflows/ci.yml | 10 +++++----- .github/workflows/release.yml | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0dc0f9..f2f9109 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,14 +16,14 @@ jobs: os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: - node-version: 20 + node-version: 22 cache: npm - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: '3.12' @@ -37,7 +37,7 @@ jobs: python python/test_ply_export.py # Caches the downloaded VS Code and test extensions between runs - - uses: actions/cache@v4 + - uses: actions/cache@v6 with: path: .vscode-test key: vscode-test-${{ runner.os }}-${{ hashFiles('.vscode-test.mjs') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 75baa4e..679c4eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,14 +13,14 @@ jobs: package: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: - node-version: 20 + node-version: 22 cache: npm - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v7 with: python-version: '3.12' @@ -43,7 +43,7 @@ jobs: - name: Package extension run: npx --yes @vscode/vsce@3 package --out python-ply-preview.vsix - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: vsix path: python-ply-preview.vsix @@ -58,9 +58,9 @@ jobs: env: OVSX_PAT: ${{ secrets.OVSX_PAT }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: name: vsix From e7fc4267954d410b37fad4af1b1acd47100eb99e Mon Sep 17 00:00:00 2001 From: cmaranes Date: Thu, 22 Oct 2026 16:32:40 +0200 Subject: [PATCH 2/3] style: fix naming-convention and curly lint warnings Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JBDmV8VxTwVxxpuC8NMG8y --- src/ViewPLYService.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ViewPLYService.ts b/src/ViewPLYService.ts index 3b34c0f..ef64361 100644 --- a/src/ViewPLYService.ts +++ b/src/ViewPLYService.ts @@ -20,9 +20,9 @@ and (o.shape[-1] in (3, 4, 6) or o.shape[-2] in (3, 4, 6))))\ // --- Service Class --- export default class ViewPLYService { - private static readonly LOG_PREFIX = '[ViewPLY]'; - private static readonly PREVIEW_FOLDER = 'ply_preview'; - private static readonly VSCODE_FOLDER = '.vscode'; + private static readonly logPrefix = '[ViewPLY]'; + private static readonly previewFolder = 'ply_preview'; + private static readonly vscodeFolder = '.vscode'; private readonly baseStorageDir: string; private readonly exportScript: string; @@ -30,7 +30,7 @@ export default class ViewPLYService { public constructor(extensionPath: string) { const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? "."; - this.baseStorageDir = join(workspaceFolder, ViewPLYService.VSCODE_FOLDER, ViewPLYService.PREVIEW_FOLDER); + this.baseStorageDir = join(workspaceFolder, ViewPLYService.vscodeFolder, ViewPLYService.previewFolder); this.exportScript = readFileSync(join(extensionPath, 'python', 'ply_export.py'), 'utf8'); // Create an Output Channel for better debugging visibility @@ -92,7 +92,9 @@ export default class ViewPLYService { } const wordRange = document.getWordRangeAtPosition(range.start); - if (!wordRange) return; + if (!wordRange) { + return; + } const expression = document.getText(wordRange); const lineText = document.lineAt(range.start.line).text; @@ -196,7 +198,7 @@ export default class ViewPLYService { private logInfo(message: string): void { const timestamp = new Date().toLocaleTimeString(); - const msg = `${ViewPLYService.LOG_PREFIX} [${timestamp}] ${message}`; + const msg = `${ViewPLYService.logPrefix} [${timestamp}] ${message}`; console.log(msg); this.outputChannel.appendLine(msg); } @@ -204,7 +206,7 @@ export default class ViewPLYService { private logError(message: string, error: any): void { const errorMessage = error instanceof Error ? error.message : String(error); const timestamp = new Date().toLocaleTimeString(); - const msg = `${ViewPLYService.LOG_PREFIX} [${timestamp}] ERROR: ${message} | Details: ${errorMessage}`; + const msg = `${ViewPLYService.logPrefix} [${timestamp}] ERROR: ${message} | Details: ${errorMessage}`; console.error(msg); this.outputChannel.appendLine(msg); } From 3823afa88133bc60fe5ddc47b8a702ee4dfc7973 Mon Sep 17 00:00:00 2001 From: cmaranes Date: Thu, 22 Oct 2026 16:32:41 +0200 Subject: [PATCH 3/3] test: harden the debugpy integration suite against CI flakes - The fixture stops via breakpoint() instead of an editor breakpoint, which raced with debugpy startup on slow runners. - On CI the debugger still occasionally (~1 in 25 jobs) never reports the stop. The suite now retries the debugger start once, and a failure includes the Debug Adapter Protocol traffic and the activeStackItem state so the cause can be diagnosed if it recurs. - Clear the preview folder first: a manual F5 session that never terminated left a .ply that the file-count assertions counted. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JBDmV8VxTwVxxpuC8NMG8y --- src/test/extension.test.ts | 109 ++++++++++++++++++++++------- test-fixtures/workspace/fixture.py | 3 +- 2 files changed, 87 insertions(+), 25 deletions(-) diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 250a961..e40421d 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -1,6 +1,6 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; -import { existsSync, readdirSync, readFileSync } from 'fs'; +import { existsSync, readdirSync, readFileSync, rmSync } from 'fs'; import { join } from 'path'; const VIEW_COMMAND = 'python-ply-preview.view'; @@ -29,17 +29,79 @@ function readVertexCount(path: string): number { /** Resolves once the debugger is paused on a stack frame. */ function waitForStop(): Promise { return new Promise((resolve, reject) => { - const timer = setTimeout(() => reject(new Error('Debugger did not stop at the breakpoint')), 60000); + const done = () => { + clearTimeout(timer); + listener.dispose(); + }; + const timer = setTimeout(() => { + done(); + const item = vscode.debug.activeStackItem; + const state = item instanceof vscode.DebugStackFrame ? 'frame' : item instanceof vscode.DebugThread ? 'thread' : 'none'; + reject(new Error(`Debugger did not stop at the breakpoint (activeStackItem: ${state}).\n` + + `Debug adapter log:\n${adapterLog.join('\n')}`)); + }, 60000); const listener = vscode.debug.onDidChangeActiveStackItem(item => { if (item instanceof vscode.DebugStackFrame) { - clearTimeout(timer); - listener.dispose(); + done(); resolve(); } }); }); } +async function startDebuggingAndWaitForStop(): Promise { + // fixture.py calls breakpoint(): an editor breakpoint races with debugpy startup on slow CI runners + const stopped = waitForStop(); + // The timeout can fire before `await stopped` below; it is still rethrown there + stopped.catch(() => undefined); + const started = await vscode.debug.startDebugging(vscode.workspace.workspaceFolders![0], { + type: 'debugpy', + name: 'ViewPLY integration test', + request: 'launch', + program: fixturePath, + // Resolved in .vscode-test.mjs + python: process.env.VIEWPLY_TEST_PYTHON, + console: 'internalConsole', + justMyCode: true, + }); + assert.ok(started, 'Debug session failed to start'); + await stopped; +} + +// Debug Adapter Protocol traffic with debugpy, reported when the debugger fails to stop +const adapterLog: string[] = []; +const adapterLogStart = Date.now(); + +function logAdapter(line: string): void { + adapterLog.push(`+${Date.now() - adapterLogStart}ms ${line}`); + if (adapterLog.length > 100) { + adapterLog.shift(); + } +} + +function trackDebugAdapter(): vscode.Disposable { + return vscode.debug.registerDebugAdapterTrackerFactory('debugpy', { + createDebugAdapterTracker: () => ({ + onWillReceiveMessage: (message: any) => { + if (message.type === 'request') { + logAdapter(`-> ${message.command}`); + } + }, + onDidSendMessage: (message: any) => { + if (message.type === 'event') { + const detail = message.event === 'output' ? `: ${String(message.body?.output).trim().slice(0, 300)}` + : message.event === 'stopped' ? ` (${message.body?.reason})` : ''; + logAdapter(`<- event ${message.event}${detail}`); + } else if (message.type === 'response' && !message.success) { + logAdapter(`<- ${message.command} failed: ${message.message}`); + } + }, + onError: error => logAdapter(`adapter error: ${error.message}`), + onExit: (code, signal) => logAdapter(`adapter exited: code=${code} signal=${signal}`), + }), + }); +} + /** Returns the range of the first occurrence of `text` in the document. */ function rangeOf(document: vscode.TextDocument, text: string): vscode.Range { const offset = document.getText().indexOf(text); @@ -57,34 +119,33 @@ async function codeActionTitles(document: vscode.TextDocument, range: vscode.Ran suite('View PLY during a debug session', () => { let document: vscode.TextDocument; + let adapterTracker: vscode.Disposable; + + suiteSetup(async function () { + // Room for one retry of the debugger start below + this.timeout(180000); + adapterTracker = trackDebugAdapter(); + + // Start clean: manual F5 runs in this folder can leave files from sessions that never terminated + rmSync(previewDir, { recursive: true, force: true }); - suiteSetup(async () => { document = await vscode.workspace.openTextDocument(fixturePath); await vscode.window.showTextDocument(document); - const breakpointLine = document.getText().split(/\r?\n/).findIndex(line => line.includes('# BREAKPOINT')); - vscode.debug.addBreakpoints([ - new vscode.SourceBreakpoint(new vscode.Location(document.uri, new vscode.Position(breakpointLine, 0))), - ]); - - const stopped = waitForStop(); - const started = await vscode.debug.startDebugging(vscode.workspace.workspaceFolders![0], { - type: 'debugpy', - name: 'ViewPLY integration test', - request: 'launch', - program: fixturePath, - // Resolved in .vscode-test.mjs - python: process.env.VIEWPLY_TEST_PYTHON, - console: 'internalConsole', - justMyCode: true, - }); - assert.ok(started, 'Debug session failed to start'); - await stopped; + // On CI runners debugpy occasionally (~1 in 25 runs) never reports the stop; retry once and keep the + // adapter log of the failed attempt so a recurring problem still surfaces + try { + await startDebuggingAndWaitForStop(); + } catch (error) { + console.warn(`First debugger start failed, retrying.\n${error instanceof Error ? error.message : error}`); + await vscode.debug.stopDebugging(); + await startDebuggingAndWaitForStop(); + } }); suiteTeardown(async () => { await vscode.debug.stopDebugging(); - vscode.debug.removeBreakpoints(vscode.debug.breakpoints); + adapterTracker.dispose(); }); test('code action is offered for point clouds without writing a file', async () => { diff --git a/test-fixtures/workspace/fixture.py b/test-fixtures/workspace/fixture.py index 353ffff..20e5928 100644 --- a/test-fixtures/workspace/fixture.py +++ b/test-fixtures/workspace/fixture.py @@ -8,7 +8,8 @@ def main(): - print(pts, pts_rgb, lidar, T4x4, subset) # BREAKPOINT + breakpoint() # debugpy stops here; no editor breakpoint needed + print(pts, pts_rgb, lidar, T4x4, subset) main()