Skip to content

Add LSP Client and long running tests in tools#511

Open
chrisqm-dev wants to merge 9 commits intomainfrom
long-running
Open

Add LSP Client and long running tests in tools#511
chrisqm-dev wants to merge 9 commits intomainfrom
long-running

Conversation

@chrisqm-dev
Copy link
Copy Markdown
Contributor

Description of changes:

  • Add lsp client methods to start up and interact with the cfn language server
  • Can be used for telemetry generator as well
  • Add long-running tests
    • This tests the standalone.js and ensures it is usable for extended periods of time
    • Tests the following:
      • Completion on document updates
      • Hover on document updates
      • Region switching, and therefore public schema retrieval
      • Ensures performance doesn't degrade over time

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@chrisqm-dev chrisqm-dev requested a review from a team as a code owner March 31, 2026 21:53
Comment thread tools/lspClient/LspClient.ts Outdated
const output = data.toString().trim();

// Readiness detection
if (output.includes('cfn-lint version')) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the only way to determine if lint, guard, and the schemas are ready is through the logs. #507 Introduces a new LSP request which will return the guard, lint, and schema statuses. This addition will make testing more robust and speed up test time, but this PR is functional without this change

@satyakigh
Copy link
Copy Markdown
Collaborator

Our folder and file names are usually camelCased unless its a bin/cli command file

Comment thread tools/lsp-client/index.ts Outdated
Comment thread tools/lsp-client/types.ts Outdated
Comment thread tools/lsp-client/types.ts Outdated
Comment thread tools/lsp-client/types.ts Outdated
Comment thread .gitignore Outdated
export type StandaloneTestMetrics = {
operationsAttempted: number;
operationsFailed: number;
averageDuration: number | null;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't these just be an array of numbers and the user decides what to do with them

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because this test could run for a very long time and there are thousands of operations per minute, the duration array could get very large. We should keep the min, max, and average attributes to avoid having to calculate from the array. Added the durations array in case there's any other operations we use in the future, such as median or p99.

Comment thread tools/stability/Monitoring.ts Outdated
}
}

export function generateFinalReport(testStartTime: number): void {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will this report be used for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This prints out to the github actions logs upon completing successfully. I wanted to replicate a test coverage output report similar to what you see in vitest. We can't use vitest since it will not work with node 18

}
}

void main();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will swallow the exception and succeed the run

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added logic so that the exception is not swallowed in the event an exception (such as hover validation failure) occurs, and it fails fast. In the catch, we will explicitly throw the error

Comment thread tools/long-running/Templates.ts Outdated
Comment thread tools/long-running/TestOrchestrator.ts Outdated
@satyakigh
Copy link
Copy Markdown
Collaborator

Lots of references to "long-running" the caller of the main file defines the duration

@chrisqm-dev
Copy link
Copy Markdown
Contributor Author

Our folder and file names are usually camelCased unless its a bin/cli command file

Fixed folder and file names so that folders are camelCased and files are PascalCased, which matches the styling in the rest of the package

@chrisqm-dev
Copy link
Copy Markdown
Contributor Author

Lots of references to "long-running" the caller of the main file defines the duration

Changed to "Stability" tests instead of calling it LongRunning

@chrisqm-dev chrisqm-dev marked this pull request as draft April 7, 2026 20:54
@chrisqm-dev chrisqm-dev marked this pull request as ready for review April 22, 2026 20:36
@chrisqm-dev chrisqm-dev force-pushed the long-running branch 2 times, most recently from bdd25b8 to 9375221 Compare April 28, 2026 18:09
rongyamazon
rongyamazon previously approved these changes Apr 28, 2026
async testAllScenarios(uri: string): Promise<void> {
// Test 1: Top-level completion using full document update
const basicTemplate = `AWSTemplateFormatVersion: '2010-09-09'
`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove extra line

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the '`' is placed on a new line so that the completion provider returns all the top level resources, since the completion provider will only return these values on a new line

Comment thread .gitignore
*.node
.DS_Store
tools/*
!tools/*.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just need !tools/**/*.ts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed !tools/**/*.ts

Comment thread tools/lspClient/LspClient.ts Outdated
const output = data.toString().trim();

// Log filtering - keep for debugging
const suppressLevels = this.config.suppressLogLevels ?? ['INFO', 'DEBUG'];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can be moved out of the function, this only needs to execute once

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved suppressLevels outside of the function

Comment thread tools/lspClient/LspClient.ts Outdated
protected isShutdown = false;
protected workspaceConfig: Record<string, unknown>[] = [{}];

constructor(protected config: LspClientConfig) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

protected?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed config to private

Comment thread tools/stability/Config.ts
path: string;
}

function parseSimpleArgs(): Partial<Config> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use yargs similar to the other tools

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yargs cannot be used in node 18:

Error: yargs parser supports a minimum Node.js version of 20. Read our version support policy: https://github.com/yargs/yargs-parser#supported-nodejs-versions

Comment thread tools/stability/Config.ts
@@ -0,0 +1,72 @@
export interface Config {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should just return a Config object, instead of exporting the parsers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed so that Config.ts exports a config object instead of parseConfig()

Comment thread tools/stability/Templates.ts Outdated
content: string;
}

export const TEMPLATE_CONFIGS: TemplateConfig[] = [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Templates? Test Templates?
There is no config here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed const to TEST_TEMPLATES

Comment thread tools/stability/testers/TesterTypes.ts Outdated
},
};

export function getTesterConfig(operationType: OperationType): TesterConfig {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If getTesterConfig is required, we don't need to export TESTER_CONFIG, but easier would just be to export TESTER_CONFIG and use it directly TESTER_CONFIG[operationType]

calling getTesterConfig or TESTER_CONFIG[operationType] once is trivial

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed getter function to instead use the const directly

@@ -0,0 +1,27 @@
import { recordOperation } from '../Monitoring';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Utils

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed file to "TesterUtils.ts"

run: node cfn-lsp-server-standalone.js --version

- name: Install dependencies
run: npm ci
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably won't work for node 18

Copy link
Copy Markdown
Contributor Author

@chrisqm-dev chrisqm-dev Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed this works for node 18 by running npm ci locally with node 18

contents: read

jobs:
check-beta-release:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this handles the main branch as well? Alpha?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a workflow for alpha to work. We need a release for this test to work as it requires a pre-built standalone file, which is not created on every push to main.

@chrisqm-dev chrisqm-dev force-pushed the long-running branch 2 times, most recently from 3b40c92 to f3210fd Compare May 1, 2026 16:33
Copy link
Copy Markdown
Collaborator

@kddejong kddejong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few comments below, requesting changes for the report timing bug.

Comment thread tools/stability/runStabilityTest.ts Outdated
try {
await orchestrator.initialize();
await orchestrator.runTests();
generateFinalReport(Date.now());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateFinalReport(Date.now()) is called after the tests finish, so the reported runtime will always be ~0 minutes. Capture the start time before runTests() and pass that in. (Or just use the startTime that initializeMonitoring() already tracks internally.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed to use startTime value appropriately

export interface LspConnection {
initialize(): Promise<void>;
sendRequest(method: string, params: any): Promise<any>;
sendNotification(method: string, params: any): Promise<void>;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of any throughout the client and testers — is there a reason we're not using the types from vscode-languageserver-protocol? Things like Hover, CompletionList, CompletionParams, etc. would give you compile-time safety on the validators for free.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added typing for the specific lsp requests.

For sendRequest, sendNotification, and onRequest,any will still be necessary since it can be used for any request object.

metric.averageDuration === null
? duration
: (metric.averageDuration * (metric.operations - 1) + duration) / metric.operations;
metric.minDuration = metric.minDuration === null ? duration : Math.min(metric.minDuration, duration);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

durations is pushed to on every operation but never read anywhere. What's it for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was originally going to be used to generate metrics other than average (for example, median duration which requires the durations list). Those metrics were not implemented since average was sufficient. Removed the durations list, we can add it back if we later determine more metrics are needed


await this.client.updateDocument(uri, 2, s3Template);

await retryOperationWithPerformance(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the document versions (2, 3, 4, 5, 6) are hardcoded across the testers and orchestrator. Works fine now but could get fragile if more testers are added. A shared counter would help down the road.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added nextDocumentVersion() and resetDocumentVersion() helpers

Copy link
Copy Markdown
Collaborator

@kddejong kddejong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ship it 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants