Initial implementation

This commit is contained in:
Robin Westberg 2022-11-02 13:26:53 +01:00 committed by Salah Benmoussati
parent ae6c7477a5
commit 8ea54fbffc
6 changed files with 202 additions and 10 deletions

166
spec/c9ShellHandler.spec.ts Normal file
View File

@ -0,0 +1,166 @@
describe('C9 shell handler', () => {
const webContentsMocked = { send: jest.fn() };
const mockSpawnEvents = new Map<String, any>();
const mockSpawn = jest.fn();
const mockGetCommandLineArgs = jest.fn();
const mockGetGuid = jest.fn();
const mockKill = jest.fn();
let mockIsWindows: boolean;
jest.mock('child_process', () => {
return {
spawn: mockSpawn,
ChildProcess: () => {},
};
});
jest.mock('../src/common/utils', () => {
return {
getCommandLineArgs: mockGetCommandLineArgs,
getGuid: mockGetGuid,
};
});
jest.mock('../src/common/env', () => {
return {
isDevEnv: false,
isElectronQA: true,
isMac: false,
isWindowsOS: mockIsWindows,
isLinux: false,
};
});
beforeEach(() => {
jest.clearAllMocks().resetModules().resetAllMocks();
mockSpawnEvents.clear();
mockSpawn.mockImplementation((_cmd, _args) => {
return {
on: (event, callback) => {
mockSpawnEvents.set(event, callback);
},
stdout: { on: jest.fn() },
stderr: { on: jest.fn() },
kill: mockKill,
};
});
mockIsWindows = true;
});
describe('launch', () => {
it('success', () => {
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'starting' },
});
mockSpawnEvents.get('spawn')();
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: expect.objectContaining({ status: 'active' }),
});
});
it('failure', () => {
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'starting' },
});
mockSpawnEvents.get('close')(1);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: expect.objectContaining({ status: 'inactive' }),
});
});
it('with attach', () => {
mockGetCommandLineArgs.mockReturnValue('--c9pipe=custompipe');
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'active', pipeName: 'symphony-c9-custompipe' },
});
});
it('cached status on relaunch', () => {
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'starting' },
});
mockSpawnEvents.get('spawn')();
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: expect.objectContaining({ status: 'active' }),
});
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: expect.objectContaining({ status: 'active' }),
});
});
it('args', () => {
mockGetGuid.mockReturnValue('just-another-guid');
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(mockSpawn).toBeCalledWith(
expect.stringContaining('c9shell.exe'),
['--symphonyHost', 'just-another-guid'],
{ stdio: 'pipe' },
);
});
it('non-windows', () => {
mockIsWindows = false;
const { loadC9Shell } = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(mockSpawn).not.toBeCalled();
});
});
describe('terminate', () => {
it('success', () => {
const {
loadC9Shell,
terminateC9Shell,
} = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'starting' },
});
terminateC9Shell(webContentsMocked as any);
expect(mockKill).toBeCalledTimes(1);
});
it('no terminate if never started', () => {
const { terminateC9Shell } = require('../src/app/c9-shell-handler');
terminateC9Shell(webContentsMocked as any);
expect(mockKill).toBeCalledTimes(0);
});
it('no terminate if already exited', () => {
const {
loadC9Shell,
terminateC9Shell,
} = require('../src/app/c9-shell-handler');
loadC9Shell(webContentsMocked as any);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: { status: 'starting' },
});
mockSpawnEvents.get('close')(1);
expect(webContentsMocked.send).lastCalledWith('c9-status-event', {
status: expect.objectContaining({ status: 'inactive' }),
});
terminateC9Shell(webContentsMocked as any);
expect(mockKill).toBeCalledTimes(0);
});
});
});

View File

@ -39,14 +39,21 @@ class C9ShellHandler {
*/
public setStatusCallback(callback: StatusCallback) {
this._statusCallback = callback;
if (!this._statusCallback) {
return;
}
if (this._curStatus) {
this._statusCallback(this._curStatus);
}
}
/**
* Terminates the c9shell process if it was started by this handler.
*/
public terminateShell() {
if (!this._c9shell) {
return;
}
this._c9shell.kill();
}
/**
* Update the current shell status and notify the callback if set.
*/
@ -107,12 +114,7 @@ class C9ShellHandler {
const c9Shell = spawn(
c9ShellPath,
[
'--allowmultiproc',
'--symphonyHost',
uniquePipeName,
...customC9ShellArgList,
],
['--symphonyHost', uniquePipeName, ...customC9ShellArgList],
{
stdio: 'pipe',
},
@ -159,3 +161,13 @@ export const loadC9Shell = (sender: WebContents) => {
});
c9ShellHandler.startShell();
};
/**
* Terminates the C9 shell process asynchronously, if it is running.
*/
export const terminateC9Shell = (_sender: WebContents) => {
if (!c9ShellHandler) {
return;
}
c9ShellHandler.terminateShell();
};

View File

@ -19,7 +19,7 @@ import { activityDetection } from './activity-detection';
import { analytics } from './analytics-handler';
import appStateHandler from './app-state-handler';
import { closeC9Pipe, connectC9Pipe, writeC9Pipe } from './c9-pipe-handler';
import { loadC9Shell } from './c9-shell-handler';
import { loadC9Shell, terminateC9Shell } from './c9-shell-handler';
import { getCitrixMediaRedirectionStatus } from './citrix-handler';
import { CloudConfigDataTypes, config, ICloudConfig } from './config-handler';
import { downloadHandler } from './download-handler';
@ -378,6 +378,9 @@ ipcMain.on(
case apiCmds.launchCloud9:
loadC9Shell(event.sender);
break;
case apiCmds.terminateCloud9:
terminateC9Shell(event.sender);
break;
case apiCmds.updateAndRestart:
autoUpdate.updateAndRestart();
break;

View File

@ -66,6 +66,7 @@ export enum apiCmds {
getCitrixMediaRedirectionStatus = 'get-citrix-media-redirection-status',
getSources = 'getSources',
launchCloud9 = 'launch-cloud9',
terminateCloud9 = 'terminate-cloud9',
connectCloud9Pipe = 'connect-cloud9-pipe',
writeCloud9Pipe = 'write-cloud9-pipe',
closeCloud9Pipe = 'close-cloud9-pipe',

View File

@ -91,6 +91,7 @@ if (ssfWindow.ssf) {
ssfWindow.ssf.getCitrixMediaRedirectionStatus,
registerClientBanner: ssfWindow.ssf.registerClientBanner,
launchCloud9: ssfWindow.ssf.launchCloud9,
terminateCloud9: ssfWindow.ssf.terminateCloud9,
connectCloud9Pipe: ssfWindow.ssf.connectCloud9Pipe,
updateAndRestart: ssfWindow.ssf.updateAndRestart,
downloadUpdate: ssfWindow.ssf.downloadUpdate,

View File

@ -798,6 +798,15 @@ export class SSFApi {
});
}
/**
* Terminates the Cloud9 client.
*/
public terminateCloud9(): void {
ipcRenderer.send(apiName.symphonyApi, {
cmd: apiCmds.terminateCloud9,
});
}
/**
* Allows JS to install new update and restart SDA
*/