Skip to content

Commit

Permalink
feat: Infrastructure for remote configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
slavek-kucera authored Jun 6, 2023
1 parent 98da01d commit 818881d
Show file tree
Hide file tree
Showing 41 changed files with 1,259 additions and 138 deletions.
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## ****Unreleased****

#### Added
- Infrastructure for remote configuration

#### Fixed
- Bridge for Git configuration files may be ignored by the macro tracer
Expand Down
9 changes: 8 additions & 1 deletion clients/vscode-hlasmplugin/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ConfigurationsHandler } from './configurationsHandler';
import { getLanguageClientMiddleware } from './languageClientMiddleware';
import { ClientInterface, ClientUriDetails, HLASMExternalFiles } from './hlasmExternalFiles';
import { HLASMExternalFilesFtp } from './hlasmExternalFilesFtp';
import { HLASMExternalConfigurationProvider, HLASMExternalConfigurationProviderHandler } from './hlasmExternalConfigurationProvider';

export const EXTENSION_ID = "broadcommfd.hlasm-language-support";

Expand Down Expand Up @@ -109,6 +110,9 @@ export async function activate(context: vscode.ExtensionContext) {

clientErrorHandler.defaultHandler = hlasmpluginClient.createDefaultErrorHandler();

const extConfProvider = new HLASMExternalConfigurationProvider(hlasmpluginClient)
context.subscriptions.push(extConfProvider);

// The objectToString is necessary, because telemetry reporter only takes objects with
// string properties and there are some boolean that we receive from the language server
hlasmpluginClient.onTelemetry((object) => { telemetry.reportEvent(object.method_name, objectToString(object.properties), object.measurements) });
Expand Down Expand Up @@ -146,7 +150,10 @@ export async function activate(context: vscode.ExtensionContext) {
},
registerExternalFileClient<ConnectArgs, ReadArgs extends ClientUriDetails, ListArgs extends ClientUriDetails>(service: string, client: ClientInterface<ConnectArgs, ReadArgs, ListArgs>) {
extFiles.setClient(service, client);
}
},
registerExternalConfigurationProvider(h: HLASMExternalConfigurationProviderHandler) {
return extConfProvider.addHandler(h);
},
};
return api;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2023 Broadcom.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*/

import * as vscode from 'vscode';
import * as vscodelc from 'vscode-languageclient';

interface ExternalConfigurationRequest {
uri: string;
};
interface ProcGrpsSchemaJson { };
interface ExternalConfigurationResponse {
configuration: string | ProcGrpsSchemaJson;
};

export type HLASMExternalConfigurationProviderHandler = (uri: vscode.Uri) => PromiseLike<ExternalConfigurationResponse | null> | ExternalConfigurationResponse | null;

function isExternalConfigurationRequest(p: any): p is ExternalConfigurationRequest {
return typeof p === 'object' && ('uri' in p);
}

function isError(e: any): e is Error {
return e instanceof Error;
}

export class HLASMExternalConfigurationProvider {
private toDispose: vscode.Disposable[] = [];
private requestHandlers: HLASMExternalConfigurationProviderHandler[] = [];

constructor(
private channel: {
onRequest<R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable;
sendNotification(method: string, params: any): Promise<void>;
}) {
this.toDispose.push(this.channel.onRequest('external_configuration_request', (...params: any[]) => this.handleRawMessage(...params)));
}

dispose() {
this.toDispose.forEach(x => x.dispose());
}

private async handleRequest(uri: vscode.Uri): Promise<ExternalConfigurationResponse | vscodelc.ResponseError> {
for (const h of this.requestHandlers) {
try {
const resp = await h(uri);
if (resp)
return resp;
}
catch (e) {
return new vscodelc.ResponseError(-106, isError(e) ? e.message : 'Unknown error');
}
}

return new vscodelc.ResponseError(0, 'Not found');
}

public async handleRawMessage(...params: any[]): Promise<ExternalConfigurationResponse | vscodelc.ResponseError> {
if (params.length < 1 || !isExternalConfigurationRequest(params[0]))
return new vscodelc.ResponseError(-5, 'Invalid request');

try {
const uri = vscode.Uri.parse(params[0].uri);
return this.handleRequest(uri);
} catch (e) {
return new vscodelc.ResponseError(-5, 'Invalid request');
}
}

private invalidateConfiguration(uri: vscode.Uri | null) {
return this.channel.sendNotification('invalidate_external_configuration', uri ? { uri: uri.toString() } : {});
}

public addHandler(h: HLASMExternalConfigurationProviderHandler) {
this.requestHandlers.push(h);

return {
dispose: () => {
const idx = this.requestHandlers.indexOf(h);
if (idx >= 0)
this.requestHandlers.splice(idx, 1);
return this.invalidateConfiguration(null);
},
invalidate: (uri: vscode.Uri | null) => this.invalidateConfiguration(uri)
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2023 Broadcom.
* The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Broadcom, Inc. - initial API and implementation
*/

import * as vscode from 'vscode';
import * as vscodelc from 'vscode-languageclient';
import * as assert from 'assert';
import { HLASMExternalConfigurationProvider } from '../../hlasmExternalConfigurationProvider';

suite('External configuration provider', () => {
test('Dispose', async () => {
let disposeCalled = 0;
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { ++disposeCalled; } };
},
sendNotification: async (method: string, params: any): Promise<void> => { }
});

const h = c.addHandler(async (uri) => null);

assert.ok(h);
assert.strictEqual(typeof h, 'object');

c.dispose();

assert.strictEqual(disposeCalled, 1);
});

test('Query after dispose', async () => {
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { } };
},
sendNotification: async (method: string, params: any): Promise<void> => { }
});

const h = c.addHandler(async (uri) => { throw Error('Should not be called'); });

assert.ok(h);
assert.strictEqual(typeof h, 'object');

h.dispose();

const f = await c.handleRawMessage({ uri: '' });
assert.ok('code' in f);
assert.deepStrictEqual(f.code, 0);
});

test('Not found', async () => {
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { } };
},
sendNotification: async (method: string, params: any): Promise<void> => { }
});

let calledWithUri: unknown;
c.addHandler(async (uri) => {
assert.ok(!calledWithUri);
calledWithUri = uri;
return null;
});

const f = await c.handleRawMessage({ uri: 'schema:path' });
assert.ok('code' in f);
assert.deepStrictEqual(f.code, 0);

assert.ok(calledWithUri instanceof vscode.Uri);
assert.deepStrictEqual(calledWithUri.toString(), 'schema:path');
});

test('Return configuration', async () => {
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { } };
},
sendNotification: async (method: string, params: any): Promise<void> => { }
});

let calledWithUri: unknown;
c.addHandler(async (uri) => {
assert.ok(!calledWithUri);
calledWithUri = uri;
return { configuration: 'PROCGRP' };
});

const f = await c.handleRawMessage({ uri: 'schema:path' });
assert.deepStrictEqual(f, { configuration: 'PROCGRP' });

assert.ok(calledWithUri instanceof vscode.Uri);
assert.deepStrictEqual(calledWithUri.toString(), 'schema:path');
});

test('Invalidation', async () => {
let notificationParam: unknown;
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { } };
},
sendNotification: async (method: string, params: any): Promise<void> => {
assert.deepEqual(method, 'invalidate_external_configuration');
notificationParam = params;
}
});

const h = c.addHandler(async (_) => null);

await h.invalidate(null);
assert.deepStrictEqual(notificationParam, {});

await h.invalidate(vscode.Uri.parse('schema:path'));
assert.deepStrictEqual(notificationParam, { uri: 'schema:path' });
});

test('Throwing handler', async () => {
const c = new HLASMExternalConfigurationProvider({
onRequest: <R, E>(method: string, handler: vscodelc.GenericRequestHandler<R, E>): vscode.Disposable => {
return { dispose: () => { } };
},
sendNotification: async (method: string, params: any): Promise<void> => { }
});

const h = c.addHandler(async (_) => { throw Error('Error message') });

const f = await c.handleRawMessage({ uri: 'schema:path' });
assert.ok('code' in f && 'message' in f);
assert.deepStrictEqual(f.code, -106);
assert.deepStrictEqual(f.message, 'Error message');
});
});
24 changes: 24 additions & 0 deletions clients/vscode-hlasmplugin/src/test/suite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ async function primeExtension(): Promise<vscode.Disposable[]> {
};

ext.registerExternalFileClient('TEST', fileClientMock);
ext.registerExternalConfigurationProvider((uri: vscode.Uri) => {
const uriString = uri.toString();
if (uriString.includes("AAAAA"))
return {
configuration: {
name: "P1",
asm_options: {
SYSPARM: "AAAAA"
},
libs: [
{
path: "libs"
},
"copy"
]
}
};
else if (uriString.includes("BBBBB"))
return {
configuration: 'P1'
};
else
return null;
});

return [vscode.debug.registerDebugAdapterTrackerFactory('hlasm', {
createDebugAdapterTracker: function (session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterTracker> {
Expand Down
47 changes: 47 additions & 0 deletions clients/vscode-hlasmplugin/src/test/suite/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as vscode from 'vscode';
import * as path from 'path';
import * as helper from './testHelper';
import { waitForDiagnostics } from './testHelper';
import { EXTENSION_ID, activate } from '../../extension';

suite('Integration Test Suite', () => {
const workspace_file = 'open';
Expand Down Expand Up @@ -239,4 +240,50 @@ suite('Integration Test Suite', () => {
assert.ok(diags2);
assert.strictEqual(diags2.length, 0);
}).timeout(10000).slow(2500);

test('External configuration', async () => {
const testFile = (s: string) => helper.waitForDiagnosticsChange(s, async () => { await helper.showDocument(s); })
const diagsA = await testFile('AAAAA.hlasm');
assert.ok(diagsA);
assert.deepStrictEqual(diagsA.map(x => [x.code, x.message]), [['MNOTE', 'AAAAA']]);

const diagsB = await testFile('BBBBB.hlasm');
assert.ok(diagsB);
assert.deepStrictEqual(diagsB.map(x => [x.code, x.message]), [['MNOTE', 'DONE']]);

const diagsC = await testFile('CCCCC.hlasm');
assert.ok(diagsC);
assert.deepStrictEqual(diagsC.map(x => x.code), ['E049']);

const ext = await vscode.extensions.getExtension<ReturnType<typeof activate>>(EXTENSION_ID)!.activate();
const tmp = ext.registerExternalConfigurationProvider((uri: vscode.Uri) => {
const uriString = uri.toString();
if (uriString.includes("CCCCC"))
return {
configuration: {
name: "P1",
asm_options: {
SYSPARM: "AAAAA"
},
libs: [
{
path: "libs"
},
"copy"
]
}
};
else
return null;
});

const newDiagsC = await helper.waitForDiagnosticsChange('CCCCC.hlasm', () => tmp.invalidate(null));
assert.ok(newDiagsC);
assert.deepStrictEqual(newDiagsC.length, 0);

const newNewDiagsC = await helper.waitForDiagnosticsChange('CCCCC.hlasm', () => tmp.dispose());
assert.ok(newNewDiagsC);
assert.deepStrictEqual(newNewDiagsC.map(x => x.code), ['E049']);

}).timeout(10000).slow(2500);
});
2 changes: 1 addition & 1 deletion clients/vscode-hlasmplugin/src/test/suite/testHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function getWorkspacePath(): string {
export async function getWorkspaceFile(workspace_file: string) {
const files = await vscode.workspace.findFiles(workspace_file);

assert.ok(files && files[0]);
assert.ok(files && files[0], workspace_file);

return files[0];
}
Expand Down
14 changes: 14 additions & 0 deletions clients/vscode-hlasmplugin/src/test/workspace/AAAAA.hlasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
MAC 1

MACRO
TEST
MNOTE 4,'&SYSPARM'
MEND
TEST







3 changes: 3 additions & 0 deletions clients/vscode-hlasmplugin/src/test/workspace/BBBBB.hlasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
MAC 1

MNOTE 'DONE'
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/src/test/workspace/CCCCC.hlasm
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MAC 1
Loading

0 comments on commit 818881d

Please sign in to comment.