Skip to content

Commit 73a7846

Browse files
committed
Wait for schemas to be loaded
1 parent e07bcf3 commit 73a7846

File tree

5 files changed

+80
-78
lines changed

5 files changed

+80
-78
lines changed

src/datastore/LMDB.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { stats } from './lmdb/Stats';
1212
import { encryptionStrategy } from './lmdb/Utils';
1313

1414
export class LMDBStoreFactory implements DataStoreFactory {
15-
private isClosed: boolean = false;
1615
private readonly log = LoggerFactory.getLogger('LMDB.Global');
1716
@Telemetry({ scope: 'LMDB.Global' }) private readonly telemetry!: ScopedTelemetry;
1817

@@ -87,18 +86,14 @@ export class LMDBStoreFactory implements DataStoreFactory {
8786
}
8887

8988
async close(): Promise<void> {
90-
if (this.isClosed) {
91-
return;
92-
}
89+
clearInterval(this.metricsInterval);
90+
clearTimeout(this.timeout);
9391

9492
// Clear the stores map but don't close individual stores
9593
// LMDB will close them when we close the environment
96-
clearInterval(this.metricsInterval);
97-
clearTimeout(this.timeout);
9894
this.stores.clear();
9995
await this.env.flushed;
10096
await this.env.close();
101-
this.isClosed = true;
10297
}
10398

10499
private cleanupOldVersions(): void {

src/server/CfnServer.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ import { ServerComponents } from './ServerComponents';
6161

6262
const log = LoggerFactory.getLogger('CfnServer');
6363
export class CfnServer {
64-
private isClosed: boolean = false;
6564
private readonly components: ServerComponents;
6665

6766
constructor(
@@ -290,10 +289,6 @@ export class CfnServer {
290289
}
291290

292291
async close(): Promise<void> {
293-
if (this.isClosed) {
294-
return;
295-
}
296292
await closeSafely(this.providers, this.external, this.core);
297-
this.isClosed = true;
298293
}
299294
}

tst/utils/TestExtension.ts

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { MultiDataStoreFactoryProvider } from '../../src/datastore/DataStore';
5353
import { FeatureFlagProvider } from '../../src/featureFlag/FeatureFlagProvider';
5454
import { LspCapabilities } from '../../src/protocol/LspCapabilities';
5555
import { LspConnection } from '../../src/protocol/LspConnection';
56+
import { SamStoreKey } from '../../src/schema/SamSchemas';
5657
import { SchemaRetriever } from '../../src/schema/SchemaRetriever';
5758
import { SchemaStore } from '../../src/schema/SchemaStore';
5859
import { CfnExternal } from '../../src/server/CfnExternal';
@@ -61,39 +62,23 @@ import { CfnLspProviders } from '../../src/server/CfnLspProviders';
6162
import { CfnServer } from '../../src/server/CfnServer';
6263
import { AwsMetadata, ExtendedInitializeParams } from '../../src/server/InitParams';
6364
import { RelationshipSchemaService } from '../../src/services/RelationshipSchemaService';
65+
import { DefaultSettings } from '../../src/settings/Settings';
6466
import { LoggerFactory } from '../../src/telemetry/LoggerFactory';
6567
import { Closeable } from '../../src/utils/Closeable';
6668
import { ExtensionName } from '../../src/utils/ExtensionConfig';
6769
import { createMockCfnLintService } from './MockServerComponents';
6870
import { getTestPrivateSchemas, samFileType, SamSchemaFiles, schemaFileType, Schemas } from './SchemaUtils';
6971
import { flushAllPromises, WaitFor } from './Utils';
7072

73+
type TestExtensionConfig = {
74+
id?: string;
75+
initializeParams?: Partial<ExtendedInitializeParams>;
76+
workspaceConfig?: Record<string, unknown>[];
77+
};
78+
7179
export class TestExtension implements Closeable {
72-
private readonly id = v4();
73-
private readonly rootDir = join(process.cwd(), 'node_modules', '.cache', 'e2e-tests', this.id);
74-
private readonly awsMetadata: AwsMetadata = {
75-
clientInfo: {
76-
extension: {
77-
name: `Test ${ExtensionName}`,
78-
version: '1.0.0-test',
79-
},
80-
clientId: this.id,
81-
},
82-
encryption: {
83-
key: randomBytes(32).toString('base64'),
84-
mode: 'JWT',
85-
},
86-
};
87-
private readonly initializeParams: ExtendedInitializeParams = {
88-
processId: process.pid,
89-
rootUri: null,
90-
capabilities: {},
91-
clientInfo: this.awsMetadata.clientInfo?.extension,
92-
workspaceFolders: [],
93-
initializationOptions: {
94-
aws: this.awsMetadata,
95-
},
96-
};
80+
private readonly awsMetadata: AwsMetadata;
81+
private readonly initializeParams: ExtendedInitializeParams;
9782

9883
private readonly readStream = new PassThrough();
9984
private readonly writeStream = new PassThrough();
@@ -108,18 +93,45 @@ export class TestExtension implements Closeable {
10893
providers!: CfnLspProviders;
10994
server!: CfnServer;
11095

111-
private lspClientReady = false;
112-
private lspServerReady = false;
96+
private isReady = false;
97+
98+
constructor(config: TestExtensionConfig = {}) {
99+
const id = config.id ?? v4();
100+
const rootDir = join(process.cwd(), 'node_modules', '.cache', 'e2e-tests', id);
101+
102+
this.awsMetadata = {
103+
clientInfo: {
104+
extension: {
105+
name: `Test ${ExtensionName}`,
106+
version: '1.0.0-test',
107+
},
108+
clientId: id,
109+
},
110+
encryption: {
111+
key: randomBytes(32).toString('base64'),
112+
mode: 'JWT',
113+
},
114+
};
115+
this.initializeParams = {
116+
processId: process.pid,
117+
rootUri: null,
118+
capabilities: {},
119+
clientInfo: this.awsMetadata.clientInfo?.extension,
120+
workspaceFolders: [],
121+
initializationOptions: {
122+
aws: this.awsMetadata,
123+
},
124+
...config.initializeParams,
125+
};
113126

114-
constructor() {
115127
this.serverConnection = new LspConnection(
116128
createConnection(new StreamMessageReader(this.readStream), new StreamMessageWriter(this.writeStream)),
117129
{
118130
onInitialize: (params) => {
119131
const lsp = this.serverConnection.components;
120132
LoggerFactory.reconfigure('warn');
121133

122-
const dataStoreFactory = new MultiDataStoreFactoryProvider(this.rootDir);
134+
const dataStoreFactory = new MultiDataStoreFactoryProvider(rootDir);
123135
this.core = new CfnInfraCore(lsp, params, {
124136
dataStoreFactory,
125137
});
@@ -154,22 +166,15 @@ export class TestExtension implements Closeable {
154166
this.server = new CfnServer(lsp, this.core, this.external, this.providers);
155167
return LspCapabilities;
156168
},
157-
onInitialized: (params) => {
158-
this.server.initialized(params);
159-
this.lspServerReady = true;
160-
},
161-
onShutdown: () => {
162-
return this.server.close();
163-
},
164-
onExit: () => {
165-
return this.server.close();
166-
},
169+
onInitialized: (params) => this.server.initialized(params),
170+
onShutdown: () => this.server.close(),
171+
onExit: () => this.server.close(),
167172
},
168173
);
169174

170175
// Handle workspace/configuration requests from the server
171176
this.clientConnection.onRequest('workspace/configuration', () => {
172-
return [{}]; // Return empty configuration
177+
return config.workspaceConfig ?? [{}];
173178
});
174179

175180
this.serverConnection.listen();
@@ -185,40 +190,39 @@ export class TestExtension implements Closeable {
185190
}
186191

187192
async ready() {
188-
if (!this.lspClientReady) {
193+
if (!this.isReady) {
189194
await this.clientConnection.sendRequest(InitializeRequest.type, this.initializeParams);
190195
await this.clientConnection.sendNotification(InitializedNotification.type, {});
191-
this.lspClientReady = true;
192-
}
193196

194-
await WaitFor.waitFor(() => {
195-
if (!this.lspServerReady) {
196-
throw new Error('Server is not ready yet');
197-
}
198-
}, 5000);
197+
await WaitFor.waitFor(() => {
198+
const store = this.external.schemaStore;
199+
const pbSchemas = store?.publicSchemas?.get(DefaultSettings.profile.region);
200+
const samSchemas = store?.samSchemas?.get(SamStoreKey);
199201

200-
await flushAllPromises();
202+
if (pbSchemas === undefined || samSchemas === undefined) {
203+
throw new Error('Schemas not loaded yet');
204+
}
205+
}, 1_000);
206+
207+
await flushAllPromises();
208+
this.isReady = true;
209+
}
201210
}
202211

203212
async send(method: string, params: any) {
204213
await this.ready();
205-
const value = await this.clientConnection.sendRequest(method, params);
206-
await wait(100);
207-
return value;
214+
return await this.clientConnection.sendRequest(method, params);
208215
}
209216

210217
async notify(method: string, params: any) {
211218
await this.ready();
212-
const value = await this.clientConnection.sendNotification(method, params);
213-
await wait(100);
214-
return value;
219+
return await this.clientConnection.sendNotification(method, params);
215220
}
216221

217222
async close() {
218223
await this.clientConnection.sendRequest(ShutdownRequest.type);
219224
await this.clientConnection.sendNotification(ExitNotification.type);
220225
this.clientConnection.dispose();
221-
await flushAllPromises();
222226
}
223227

224228
// ====================================================================
@@ -227,18 +231,22 @@ export class TestExtension implements Closeable {
227231

228232
async openDocument(params: DidOpenTextDocumentParams) {
229233
await this.notify(DidOpenTextDocumentNotification.method, params);
234+
await wait(10);
230235
}
231236

232237
async changeDocument(params: DidChangeTextDocumentParams) {
233238
await this.notify(DidChangeTextDocumentNotification.method, params);
239+
await wait(10);
234240
}
235241

236242
async closeDocument(params: DidCloseTextDocumentParams) {
237243
await this.notify(DidCloseTextDocumentNotification.method, params);
244+
await wait(10);
238245
}
239246

240-
saveDocument(params: DidSaveTextDocumentParams) {
241-
return this.notify(DidSaveTextDocumentNotification.method, params);
247+
async saveDocument(params: DidSaveTextDocumentParams) {
248+
await this.notify(DidSaveTextDocumentNotification.method, params);
249+
await wait(10);
242250
}
243251

244252
completion(params: CompletionParams) {

tst/utils/Utils.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
export function flushAllPromises() {
2-
return new Promise((resolve) => {
3-
setTimeout(resolve, 0);
4-
});
1+
import { setImmediate } from 'node:timers/promises';
2+
3+
export async function flushAllPromises() {
4+
await setImmediate();
55
}
66

77
export class WaitFor {
88
constructor(
9-
private readonly maxWaitMs: number = 25,
10-
private readonly delayIntervalMs: number = 1,
9+
private readonly maxWaitMs: number = 100,
10+
private readonly delayIntervalMs: number = 10,
1111
private readonly throwableTypesToExpect: (new (...args: any[]) => Error)[] = [Error],
1212
) {}
1313

@@ -33,7 +33,11 @@ export class WaitFor {
3333
throw lastError!;
3434
}
3535

36-
static async waitFor(code: () => void | Promise<void>, timeoutMs: number = 100): Promise<void> {
37-
await new WaitFor(timeoutMs).wait(code);
36+
static async waitFor(
37+
code: () => void | Promise<void>,
38+
timeoutMs: number = 100,
39+
intervalMs: number = 10,
40+
): Promise<void> {
41+
await new WaitFor(timeoutMs, intervalMs).wait(code);
3842
}
3943
}

0 commit comments

Comments
 (0)