// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. 'use strict'; import * as assert from 'assert'; import { ChildProcess, spawn, StdioOptions } from 'child_process'; import { Deferred } from 'ts-deferred'; import { cleanupUnitTest, prepareUnitTest, getTunerProc, getCmdPy } from '../../common/utils'; import * as CommandType from '../commands'; import { createDispatcherInterface, IpcInterface } from '../ipcInterface'; import { NNIError } from '../../common/errors'; let sentCommands: { [key: string]: string }[] = []; const receivedCommands: { [key: string]: string }[] = []; let commandTooLong: Error | undefined; let rejectCommandType: Error | undefined; function runProcess(): Promise<Error | null> { // the process is intended to throw error, do not reject const deferred: Deferred<Error | null> = new Deferred<Error | null>(); // create fake assessor process const stdio: StdioOptions = ['ignore', 'pipe', process.stderr, 'pipe', 'pipe']; const command: string = getCmdPy() + ' assessor.py'; const proc: ChildProcess = getTunerProc(command, stdio, 'core/test', process.env); // record its sent/received commands on exit proc.on('error', (error: Error): void => { deferred.resolve(error); }); proc.on('exit', (code: number): void => { if (code !== 0) { deferred.resolve(new Error(`return code: ${code}`)); } else { let str = proc.stdout.read().toString(); if(str.search("\r\n")!=-1){ sentCommands = str.split("\r\n"); } else{ sentCommands = str.split('\n'); } deferred.resolve(null); } }); // create IPC interface const dispatcher: IpcInterface = createDispatcherInterface(proc); dispatcher.onCommand((commandType: string, content: string): void => { receivedCommands.push({ commandType, content }); }); // Command #1: ok dispatcher.sendCommand('IN'); // Command #2: ok dispatcher.sendCommand('ME', '123'); // Command #3: too long try { dispatcher.sendCommand('ME', 'x'.repeat(1_000_000)); } catch (error) { commandTooLong = error; } // Command #4: FE is not tuner/assessor command, test the exception type of send non-valid command try { dispatcher.sendCommand('FE', '1'); } catch (error) { rejectCommandType = error; } return deferred.promise; } describe('core/protocol', (): void => { before(async () => { prepareUnitTest(); await runProcess(); }); after(() => { cleanupUnitTest(); }); it('should have sent 2 successful commands', (): void => { assert.equal(sentCommands.length, 3); assert.equal(sentCommands[2], ''); }); it('sendCommand() should work without content', (): void => { assert.equal(sentCommands[0], '(\'IN\', \'\')'); }); it('sendCommand() should work with content', (): void => { assert.equal(sentCommands[1], '(\'ME\', \'123\')'); }); it('sendCommand() should throw on too long command', (): void => { if (commandTooLong === undefined) { assert.fail('Should throw error') } else { const err: Error | undefined = (<NNIError>commandTooLong).cause; assert(err && err.name === 'RangeError'); assert(err && err.message === 'Command too long'); } }); it('sendCommand() should throw on wrong command type', (): void => { assert.equal((<Error>rejectCommandType).name, 'AssertionError [ERR_ASSERTION]'); }); it('should have received 3 commands', (): void => { assert.equal(receivedCommands.length, 3); }); it('onCommand() should work without content', (): void => { assert.deepStrictEqual(receivedCommands[0], { commandType: 'KI', content: '' }); }); it('onCommand() should work with content', (): void => { assert.deepStrictEqual(receivedCommands[1], { commandType: 'KI', content: 'hello' }); }); it('onCommand() should work with Unicode content', (): void => { assert.deepStrictEqual(receivedCommands[2], { commandType: 'KI', content: '世界' }); }); });