Skip to content

Commit

Permalink
fix: Retrieval of dependencies is suspended by an arbitrary error
Browse files Browse the repository at this point in the history
  • Loading branch information
slavek-kucera authored Aug 13, 2024
1 parent 88be6eb commit 1ac9985
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 17 deletions.
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Evaluate relative paths specified in settings with respect to the root folder
- PRINT instruction should tolerate null operands
- Download dependencies command supports reading processor groups from settings
- Retrieval of dependencies is suspended by an arbitrary error

## [1.14.0](https://github.com/eclipse-che4z/che-che4z-lsp-for-hlasm/compare/1.13.0...1.14.0) (2024-07-16)

Expand Down
1 change: 1 addition & 0 deletions clients/vscode-hlasmplugin/src/extension.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ClientInterface, ClientUriDetails, ExternalFilesInvalidationdata, Exter
export interface HlasmExtension {
registerExternalFileClient<ConnectArgs, ReadArgs extends ClientUriDetails, ListArgs extends ClientUriDetails>(service: string, client: Readonly<ClientInterface<ConnectArgs, ReadArgs, ListArgs>>): vscode.Disposable;
registerExternalConfigurationProvider(h: HLASMExternalConfigurationProviderHandler): ConfigurationProviderRegistration;
makeSuspendError(e: Error): Error;
};

export { ExternalRequestType, ExternalFilesInvalidationdata };
7 changes: 5 additions & 2 deletions clients/vscode-hlasmplugin/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { HLASMCodeActionsProvider } from './hlasmCodeActionsProvider';
import { hlasmplugin_folder_filter, bridge_json_filter, schemeExternalFiles, continuationColumn, initialBlanks, languageIdHlasm, debugTypeHlasm, schemeVirtualFiles } from './constants';
import { ConfigurationsHandler } from './configurationsHandler';
import { HlasmPluginMiddleware, getLanguageClientMiddleware } from './languageClientMiddleware';
import { HLASMExternalFiles } from './hlasmExternalFiles';
import { HLASMExternalFiles, SuspendError } from './hlasmExternalFiles';
import { HLASMExternalFilesFtp } from './hlasmExternalFilesFtp';
import { HLASMExternalConfigurationProvider, HLASMExternalConfigurationProviderHandler } from './hlasmExternalConfigurationProvider';
import { HlasmExtension } from './extension.interface';
Expand Down Expand Up @@ -135,6 +135,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<HlasmE
registerExternalConfigurationProvider(h: HLASMExternalConfigurationProviderHandler) {
return extConfProvider.addHandler(h);
},
makeSuspendError(e) {
return new SuspendError(e);
},
};

handleE4EIntegration(api, context.subscriptions, hlasmpluginClient.outputChannel);
Expand Down Expand Up @@ -292,7 +295,7 @@ async function registerExternalFileSupport(context: vscode.ExtensionContext, cli
extFiles.setClient('DATASET', datasetClient);

context.subscriptions.push(vscode.commands.registerCommand('extension.hlasm-plugin.resumeRemoteActivity', () => extFiles.resumeAll()));
context.subscriptions.push(vscode.commands.registerCommand('extension.hlasm-plugin.suspendRemoteActivity', () => extFiles.suspendAll()));
context.subscriptions.push(vscode.commands.registerCommand('extension.hlasm-plugin.suspendRemoteActivity', () => extFiles.suspendAll("user")));
context.subscriptions.push(vscode.commands.registerCommand('extension.hlasm-plugin.clearRemoteActivityCache', () => extFiles.clearCache()));
context.subscriptions.push(vscode.commands.registerCommand('extension.hlasm-plugin.clearRemoteActivityCacheForService', () =>
pickUser('Select service', extFiles.currentlyAvailableServices().map(x => { return { label: x, value: x }; })).then(x => extFiles.clearCache(x), () => { })
Expand Down
26 changes: 21 additions & 5 deletions clients/vscode-hlasmplugin/src/hlasmExternalFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ type ChannelType = {
sendNotification(method: string, params: any): Promise<void>;
};

export class SuspendError extends Error {
constructor(public error: Error) { super(error.message); }
}

export class HLASMExternalFiles {
private memberLists = new Map<string, CacheEntry<string[]>>();
private memberContent = new Map<string, CacheEntry<string>>();
Expand Down Expand Up @@ -354,7 +358,7 @@ export class HLASMExternalFiles {
};
}

public suspendAll() {
public suspendAll(reason: "creds" | "cancel" | "user") {
let oneSuspended = false;
this.clients.forEach(client => {
if (client.suspended) return;
Expand All @@ -368,7 +372,11 @@ export class HLASMExternalFiles {
this.activeProgress.done();
this.activeProgress = null;
}
vscode.window.showInformationMessage("Retrieval of remote files has been suspended.");
const msg = "Retrieval of remote files has been suspended.";
if (reason === "cancel" || reason === "user")
vscode.window.showInformationMessage(msg);
else if (reason === "creds")
vscode.window.showWarningMessage(msg);
}
}

Expand All @@ -381,6 +389,8 @@ export class HLASMExternalFiles {
});
}

public listClients() { return [...this.clients].map(([name, c]) => ({ name, suspended: c.suspended })); }

public getTextDocumentContentProvider(): vscode.TextDocumentContentProvider {
const me = this;
return {
Expand Down Expand Up @@ -475,9 +485,15 @@ export class HLASMExternalFiles {
}

private handleError(x: unknown) {
const e = asError(x);
this.suspendAll();
if (!isCancellationError(e))
const isSuspendError = x instanceof SuspendError;
const e = isSuspendError ? x.error : asError(x);
const isCancel = isCancellationError(e);
if (isSuspendError)
this.suspendAll("creds");
else if (isCancel)
this.suspendAll("cancel");

if (!isCancel)
vscode.window.showErrorMessage(e.message);

return { message: e.message };
Expand Down
18 changes: 14 additions & 4 deletions clients/vscode-hlasmplugin/src/hlasmExternalFilesEndevor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as vscode from 'vscode';
import { ExternalRequestType, HlasmExtension, ExternalFilesInvalidationdata } from './extension.interface';
import { AsmOptions, ConfigurationProviderRegistration, Preprocessor } from './hlasmExternalConfigurationProvider';
import { SuspendError } from './hlasmExternalFiles';

interface EndevorType {
use_map: string,
Expand Down Expand Up @@ -367,6 +368,15 @@ function profileAsString(profile: ResolvedProfile) {
return `${profile.instance}@${profile.profile}`;
}

const EndevorCredentialsErrorLegacyString = 'Unable to obtain credentials for Endevor connection';
async function translateError(e: Error): Promise<never> {
if ('credentialsError' in e && e.credentialsError === true)
throw new SuspendError(e);
if (e.message.startsWith(EndevorCredentialsErrorLegacyString))
throw new SuspendError(e);
throw e;
}

function listEndevorElements(e4e: E4E, type_spec: EndevorType, profile: ResolvedProfile) {
return e4e.listElements(profile, {
use_map: type_spec.use_map === "map",
Expand All @@ -376,7 +386,7 @@ function listEndevorElements(e4e: E4E, type_spec: EndevorType, profile: Resolved
subsystem: type_spec.subsystem,
type: type_spec.type
}).then(
r => r instanceof Error ? Promise.reject(r) : r?.map(([file, fingerprint]) => `/${encodeURIComponent(profileAsString(profile))}${type_spec.normalizedPath()}/${encodeURIComponent(file)}.hlasm?${fingerprint.toString()}`) ?? null
r => r instanceof Error ? translateError(r) : r?.map(([file, fingerprint]) => `/${encodeURIComponent(profileAsString(profile))}${type_spec.normalizedPath()}/${encodeURIComponent(file)}.hlasm?${fingerprint.toString()}`) ?? null
);
}

Expand All @@ -390,22 +400,22 @@ function readEndevorElement(e4e: E4E, file_spec: EndevorElement, profile: Resolv
type: file_spec.type,
element: file_spec.element,
fingerprint: file_spec.fingerprint,
}).then(r => r instanceof Error ? Promise.reject(r) : r[0]);
}).then(r => r instanceof Error ? translateError(r) : r[0]);
}

function listEndevorMembers(e4e: E4E, type_spec: EndevorDataset, profile: ResolvedProfile) {
return e4e.listMembers(profile, {
dataset: type_spec.dataset
}).then(
r => r instanceof Error ? Promise.reject(r) : r?.map((member) => `/${encodeURIComponent(profileAsString(profile))}${type_spec.normalizedPath()}/${encodeURIComponent(member)}.hlasm`) ?? null
r => r instanceof Error ? translateError(r) : r?.map((member) => `/${encodeURIComponent(profileAsString(profile))}${type_spec.normalizedPath()}/${encodeURIComponent(member)}.hlasm`) ?? null
);
}

function readEndevorMember(e4e: E4E, file_spec: EndevorMember, profile: ResolvedProfile) {
return e4e.getMember(profile, {
dataset: file_spec.dataset,
member: file_spec.member,
}).then(r => r instanceof Error ? Promise.reject(r) : r);
}).then(r => r instanceof Error ? translateError(r) : r);
}

function whitespaceAsUndefined(s: string) {
Expand Down
7 changes: 3 additions & 4 deletions clients/vscode-hlasmplugin/src/hlasmExternalFilesFtp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import * as vscode from 'vscode';
import * as ftp from 'basic-ftp';
import { ClientInterface, ClientUriDetails, ExternalRequestType } from './hlasmExternalFiles';
import { ClientInterface, ClientUriDetails, ExternalRequestType, SuspendError } from './hlasmExternalFiles';
import { ConnectionInfo, gatherConnectionInfo, getLastRunConfig, translateConnectionInfo, updateLastRunConfig } from './ftpCreds';
import { FBWritable } from './FBWritable';
import { ConnectionPool } from './connectionPool';
Expand Down Expand Up @@ -72,7 +72,7 @@ export function HLASMExternalFilesFtp(context: vscode.ExtensionContext): ClientI
return mutex.locked(async () => {
const info = activeConnectionInfo ?? (pool.closeClients(), await getConnInfo());
activeConnectionInfo = undefined;
await client.access(translateConnectionInfo(info));
await client.access(translateConnectionInfo(info)).catch(e => { throw e instanceof ftp.FTPError && (e.code === 430 || e.code == 530) ? new SuspendError(e) : e; });
activeConnectionInfo = info;

return client;
Expand Down Expand Up @@ -102,8 +102,7 @@ export function HLASMExternalFilesFtp(context: vscode.ExtensionContext): ClientI
listMembers: async (args: DatasetUriDetails): Promise<string[] | null> => pool.withClient(async (client) => {
try {
await checkedCommand(client, 'TYPE A');
checkResponse(await client.cd(`'${args.dataset}'`));
const list = await client.list();
const list = await client.list(`'${args.dataset}(*)'`);
return list.map(x => `/${args.dataset}/${x.name}.hlasm`);
}
catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import * as assert from 'assert';
import * as tools from '../../tools';
import { ClientUriDetails, ExternalFilesInvalidationdata, ExternalRequestType, HLASMExternalFiles } from '../../hlasmExternalFiles';
import { ClientUriDetails, ExternalFilesInvalidationdata, ExternalRequestType, HLASMExternalFiles, SuspendError } from '../../hlasmExternalFiles';
import { EventEmitter, FileSystem, Uri } from 'vscode';
import { FileType } from 'vscode';

Expand Down Expand Up @@ -318,4 +318,34 @@ suite('External files', () => {

attached.dispose();
});

test('Credentials error', async () => {
const ext = new HLASMExternalFiles('test', {} as any as FileSystem);

ext.setClient('TEST', {
parseArgs: async () => {
return {
details: {
normalizedPath: () => '/DIR',
},
server: undefined,
};
},

listMembers: (_: ClientUriDetails) => Promise.reject(new Error("creds")),
readMember: (_: ClientUriDetails) => Promise.reject(new SuspendError(new Error("creds"))),
});

assert.strictEqual(ext.listClients().reduce((s, x) => s || x.suspended, false), false);

const r1 = await ext.handleRawMessage({ id: 0, op: 'list_directory', url: 'test:/TEST/DIR' });

assert.ok(r1 && 'error' in r1);
assert.strictEqual(ext.listClients().reduce((s, x) => s || x.suspended, false), false);

const r2 = await ext.handleRawMessage({ id: 0, op: 'read_file', url: 'test:/TEST/DIR' });

assert.ok(r2 && 'error' in r2);
assert.strictEqual(ext.listClients().reduce((s, x) => s || x.suspended, false), true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import * as assert from 'assert';
import { E4E, HLASMExternalFilesEndevor, makeEndevorConfigurationProvider, processChangeNotification } from '../../hlasmExternalFilesEndevor';
import * as vscode from 'vscode';
import { ExternalRequestType } from '../../hlasmExternalFiles';
import { ExternalRequestType, SuspendError } from '../../hlasmExternalFiles';

const dummyProfile = { profile: 'profile', instance: 'instance' };
const profileOnly = {
Expand Down Expand Up @@ -226,4 +226,47 @@ suite('External files (Endevor)', () => {
},
});
});

test('Invalid credentials', async () => {
const client = HLASMExternalFilesEndevor({
listMembers: async (_p: unknown, _t: unknown) => {
return new class extends Error { public readonly credentialsError = true; constructor() { super(""); } };
}
} as any as E4E, dummyEvent);

try {
await client.listMembers({ normalizedPath: () => '/PATH' } as any, dummyProfile);
assert.ok(false);
}
catch (e) {
assert.ok(e instanceof SuspendError);
}
});

test('Invalid credentials legacy', async () => {
const client = HLASMExternalFilesEndevor({
listMembers: async (_p: unknown, _t: unknown) => Error('Unable to obtain credentials for Endevor connection xyz')
} as any as E4E, dummyEvent);

try {
await client.listMembers({ normalizedPath: () => '/PATH' } as any, dummyProfile);
assert.ok(false);
}
catch (e) {
assert.ok(e instanceof SuspendError);
}
});

test('Common error', async () => {
const error = Error('Something went wrong');
const client = HLASMExternalFilesEndevor({ listMembers: async (_p: unknown, _t: unknown) => error } as any as E4E, dummyEvent);

try {
await client.listMembers({ normalizedPath: () => '/PATH' } as any, dummyProfile);
assert.ok(false);
}
catch (e) {
assert.strictEqual(e, error);
}
});
});

0 comments on commit 1ac9985

Please sign in to comment.