From 3398bd2509a23abe150211784cbf5e8cb97b664b Mon Sep 17 00:00:00 2001 From: award999 Date: Wed, 8 Jan 2025 14:12:07 -0500 Subject: [PATCH] Fix macro expansion test (#1301) * Fix macro expansion test Wait for document to be indexed before proceeding Issue: #1286 * Catch unexpected errors * Use _pollIndex request * Some cleanup --- assets/test/swift-macro/Package.swift | 2 +- .../LanguageClientIntegration.test.ts | 38 ++++---- .../utilities/lsputilities.ts | 87 +++++++++++++++++++ 3 files changed, 105 insertions(+), 22 deletions(-) create mode 100644 test/integration-tests/utilities/lsputilities.ts diff --git a/assets/test/swift-macro/Package.swift b/assets/test/swift-macro/Package.swift index 24d3dbf6d..046839911 100644 --- a/assets/test/swift-macro/Package.swift +++ b/assets/test/swift-macro/Package.swift @@ -19,7 +19,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0-latest"), + .package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.1"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/test/integration-tests/language/LanguageClientIntegration.test.ts b/test/integration-tests/language/LanguageClientIntegration.test.ts index 7e9772228..3a2816b80 100644 --- a/test/integration-tests/language/LanguageClientIntegration.test.ts +++ b/test/integration-tests/language/LanguageClientIntegration.test.ts @@ -22,19 +22,8 @@ import { executeTaskAndWaitForResult, waitForNoRunningTasks } from "../../utilit import { getBuildAllTask, SwiftTask } from "../../../src/tasks/SwiftTaskProvider"; import { Version } from "../../../src/utilities/version"; import { activateExtensionForSuite, folderInRootWorkspace } from "../utilities/testutilities"; - -async function waitForClientState( - languageClientManager: LanguageClientManager, - expectedState: langclient.State -): Promise { - let clientState = undefined; - while (clientState !== expectedState) { - clientState = await languageClientManager.useLanguageClient(async client => client.state); - console.warn("Language client is not ready yet. Retrying in 100 ms..."); - await new Promise(resolve => setTimeout(resolve, 100)); - } - return clientState; -} +import { FolderContext } from "../../../src/FolderContext"; +import { waitForClientState, waitForCodeActions, waitForIndex } from "../utilities/lsputilities"; async function buildProject(ctx: WorkspaceContext, name: string) { await waitForNoRunningTasks(); @@ -42,31 +31,36 @@ async function buildProject(ctx: WorkspaceContext, name: string) { const task = (await getBuildAllTask(folderContext)) as SwiftTask; const { exitCode, output } = await executeTaskAndWaitForResult(task); expect(exitCode, `${output}`).to.equal(0); + return folderContext; } suite("Language Client Integration Suite @slow", function () { + this.timeout(5 * 60 * 1000); + let clientManager: LanguageClientManager; let workspaceContext: WorkspaceContext; + let macroFolderContext: FolderContext; activateExtensionForSuite({ async setup(ctx) { - this.timeout(5 * 60 * 1000); - workspaceContext = ctx; // Wait for a clean starting point, and build all tasks for the fixture if (workspaceContext.swiftVersion.isGreaterThanOrEqual(new Version(6, 1, 0))) { - await buildProject(ctx, "swift-macro"); + macroFolderContext = await buildProject(ctx, "swift-macro"); } await buildProject(ctx, "defaultPackage"); // Ensure lsp client is ready clientManager = ctx.languageClientManager; - const clientState = await waitForClientState(clientManager, langclient.State.Running); - expect(clientState).to.equals(langclient.State.Running); + await waitForClientState(clientManager, langclient.State.Running); }, }); + setup(async () => { + await waitForIndex(workspaceContext.languageClientManager); + }); + test("Expand Macro", async function () { // Expand Macro support in Swift started from 6.1 if (workspaceContext.swiftVersion.isLessThan(new Version(6, 1, 0))) { @@ -76,12 +70,15 @@ suite("Language Client Integration Suite @slow", function () { // Focus on the file of interest const uri = testAssetUri("swift-macro/Sources/swift-macroClient/main.swift"); await vscode.window.showTextDocument(uri); + await workspaceContext.focusFolder(macroFolderContext); // Beginning of macro, # const position = new vscode.Position(5, 21); // Create a range starting and ending at the specified position - const range = new vscode.Range(position, position); + const range = new vscode.Selection(position, position.with({ character: 22 })); + + await waitForCodeActions(workspaceContext.languageClientManager, uri, range); // Execute the code action provider command const codeActions = await vscode.commands.executeCommand( @@ -90,8 +87,6 @@ suite("Language Client Integration Suite @slow", function () { range ); - const expectedMacro = '(a + b, "a + b")'; - // Find the "expand.macro.command" action const expandMacroAction = codeActions.find( action => action.command?.command === "expand.macro.command" @@ -129,6 +124,7 @@ suite("Language Client Integration Suite @slow", function () { expect(referenceDocument).to.not.be.undefined; // Assert that the content contains the expected result + const expectedMacro = '(a + b, "a + b")'; const content = referenceDocument.getText(); expect(content).to.include(expectedMacro); }); diff --git a/test/integration-tests/utilities/lsputilities.ts b/test/integration-tests/utilities/lsputilities.ts new file mode 100644 index 000000000..066b791ce --- /dev/null +++ b/test/integration-tests/utilities/lsputilities.ts @@ -0,0 +1,87 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the VS Code Swift open source project +// +// Copyright (c) 2025 the VS Code Swift project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of VS Code Swift project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import * as vscode from "vscode"; +import * as langclient from "vscode-languageclient/node"; +import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager"; + +export async function waitForClient( + languageClientManager: LanguageClientManager, + getResult: ( + c: langclient.LanguageClient, + token: langclient.CancellationToken + ) => Promise, + match: (r: Result | undefined) => boolean +): Promise { + let result: Result | undefined = undefined; + while (!match(result)) { + result = await languageClientManager.useLanguageClient(getResult); + console.warn("Language client is not ready yet. Retrying in 100 ms..."); + await new Promise(resolve => setTimeout(resolve, 100)); + } + return result; +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace PollIndexRequest { + export const method = "workspace/_pollIndex" as const; + export const messageDirection: langclient.MessageDirection = + langclient.MessageDirection.clientToServer; + export const type = new langclient.RequestType(method); +} + +export async function waitForIndex(languageClientManager: LanguageClientManager): Promise { + await languageClientManager.useLanguageClient(async (client, token) => + client.sendRequest(PollIndexRequest.type, {}, token) + ); +} + +export async function waitForClientState( + languageClientManager: LanguageClientManager, + expectedState: langclient.State +): Promise { + return await waitForClient( + languageClientManager, + async c => c.state, + s => s === expectedState + ); +} + +export async function waitForCodeActions( + languageClientManager: LanguageClientManager, + uri: vscode.Uri, + range: vscode.Range +): Promise<(langclient.CodeAction | langclient.Command)[]> { + return ( + (await waitForClient( + languageClientManager, + async (client, token) => { + try { + return client.sendRequest( + langclient.CodeActionRequest.type, + { + context: langclient.CodeActionContext.create([]), + textDocument: langclient.TextDocumentIdentifier.create(uri.toString()), + range, + }, + token + ); + } catch (e) { + // Ignore + } + }, + s => (s || []).length > 0 + )) || [] + ); +}