| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| import * as vscode from 'vscode'; |
| import * as assert from 'assert'; |
| |
| import { beforeEach, describe, it } from 'mocha'; |
| import { createSandbox } from 'sinon'; |
| import { Ffx, FfxEventType, FuchsiaDevice } from '../../ffx'; |
| import { ToolFinder } from '../../tool_finder'; |
| import { StubbedSpawn } from './utils'; |
| |
| describe('FuchsiaDevice', function () { |
| describe('#constructor()', function () { |
| it('creates an instance of FuchsiaDevice from the json returned from ffx target list', |
| function () { |
| const data = { |
| 'nodename': 'test-device', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'rcs_state': 'Y', |
| 'serial': '<unknown>', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_type': 'workstation.qemu-x64', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_state': 'Product', |
| 'addresses': ['fe80::3bee:1d4:e205:777e%brqemu', '172.1.1.1'] |
| }; |
| |
| let device = new FuchsiaDevice(data); |
| assert.strictEqual(device.nodeName, data['nodename']); |
| assert.strictEqual(device.rcsState, data['rcs_state']); |
| assert.strictEqual(device.serial, data['serial']); |
| assert.strictEqual(device.targetType, data['target_type']); |
| assert.strictEqual(device.targetState, data['target_state']); |
| let testAddresses = data['addresses']; |
| let actualAddresses = device.addresses; |
| for (let i in testAddresses) { |
| assert.strictEqual(testAddresses[i], actualAddresses[i]); |
| |
| } |
| |
| }); |
| }); |
| }); |
| |
| describe('Ffx', function () { |
| const sandbox = createSandbox(); |
| const TEST_FFX = '/path/to/ffx'; |
| const TEST_CWD = '/path/to/workspace'; |
| var log: vscode.OutputChannel; |
| var toolFinder: ToolFinder; |
| var stubbedSpawn: StubbedSpawn; |
| |
| this.beforeEach(function () { |
| let logger = sandbox.spy(vscode.window.createOutputChannel); |
| log = logger('test_ffx'); |
| toolFinder = new ToolFinder(log); |
| sandbox.stub(toolFinder, 'getFfxPath').returns(TEST_FFX); |
| stubbedSpawn = new StubbedSpawn(sandbox); |
| }); |
| |
| this.afterEach(function () { |
| sandbox.restore(); |
| }); |
| describe('#constructor', function () { |
| it('creates an instance of ffx', function () { |
| new Ffx(log, TEST_CWD, TEST_FFX); |
| }); |
| }); |
| |
| describe('#rebootTarget', function () { |
| it('calls ffx target reboot for the default device successfully', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| try { |
| let promise = ffx.rebootTarget(); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| let output = await promise; |
| assert.strictEqual(output, ''); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, ['target', 'reboot'], { cwd: TEST_CWD }]); |
| } catch (err) { |
| assert.fail(`unexpected exception: ${err}`); |
| } |
| }); |
| |
| it('calls ffx target reboot for the default device and fails', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| const errorMessage = 'Cannot reboot device'; |
| |
| try { |
| let promise = ffx.rebootTarget(); |
| stubbedSpawn.spawnEvent.stderr?.emit('data', errorMessage); |
| stubbedSpawn.spawnEvent.emit('exit', 1); |
| await promise; |
| } catch (err) { |
| assert.strictEqual(`Error 1: ${errorMessage}`, err); |
| } |
| }); |
| |
| it('calls ffx target reboot the specified device', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| let device = new FuchsiaDevice({ 'nodename': 'test-device' }); |
| try { |
| let promise = ffx.rebootTarget(device); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| let output = await promise; |
| assert.strictEqual(output, ''); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, ['--target', 'test-device', 'target', 'reboot'], { cwd: TEST_CWD }]); |
| |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| }); |
| }); |
| |
| |
| describe('#showTarget', function () { |
| it('calls ffx target show for the default device successfully', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| try { |
| let promise = ffx.rebootTarget(); |
| // normal exit, no output. |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| let output = await promise; |
| |
| assert.strictEqual(output, ''); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, ['target', 'reboot'], { cwd: TEST_CWD }]); |
| |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| }); |
| |
| it('calls ffx target reboot for the default device and fails', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| const errorMessage = 'Cannot reboot device'; |
| |
| try { |
| let promise = ffx.rebootTarget(); |
| stubbedSpawn.spawnEvent.stderr?.emit('data', errorMessage); |
| stubbedSpawn.spawnEvent.emit('exit', 1); |
| await promise; |
| |
| } catch (err) { |
| assert.strictEqual(`Error 1: ${errorMessage}`, err); |
| |
| } |
| }); |
| |
| it('calls ffx target reboot the specified device', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| try { |
| let device = new FuchsiaDevice({ 'nodename': 'test-device' }); |
| let promise = ffx.rebootTarget(device); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| let output = await promise; |
| assert.strictEqual(output, ''); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, ['--target', 'test-device', 'target', 'reboot'], { cwd: TEST_CWD }]); |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| }); |
| }); |
| |
| describe('#defaultTarget', function () { |
| it('sets the specified device to be the default target', async () => { |
| |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| let device = new FuchsiaDevice({ 'nodename': 'test-device' }); |
| try { |
| let promise = ffx.defaultTarget(device); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| let output = await promise; |
| assert.strictEqual(output, ''); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, ['target', 'default', 'set', 'test-device'], { cwd: TEST_CWD }]); |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| |
| }); |
| }); |
| |
| describe('#events', function () { |
| it('Verify set path events', () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| let lastEvent: FfxEventType | undefined; |
| ffx.onDidChangeConfiguration(event => { |
| lastEvent = event; |
| }); |
| ffx.setFfxPath(TEST_FFX); |
| assert.strictEqual(lastEvent, FfxEventType.ffxPathSet); |
| ffx.setFfxPath(undefined); |
| assert.strictEqual(lastEvent, FfxEventType.ffxPathReset); |
| ffx.setFfxPath(TEST_FFX); |
| assert.strictEqual(lastEvent, FfxEventType.ffxPathSet); |
| }); |
| }); |
| |
| describe('#getTargetList', function () { |
| const targetListData = [ |
| { |
| 'nodename': 'test-device', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'rcs_state': 'Y', |
| 'serial': 'serial-11111', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_type': 'test-product', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_state': 'Product', |
| 'addresses': ['fe80::41e7:ace8:59b7:3cb7%en11'], |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'is_default': false |
| }, |
| { |
| 'nodename': 'another-device', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'rcs_state': 'Y', |
| 'serial': 'serial-222222', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_type': 'test-product', |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'target_state': 'Product', |
| 'addresses': ['fe80::1010:1010:1010:1010%en11'], |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| 'is_default': true |
| } |
| ]; |
| |
| const TARGET_LIST_ARGS = ['target', 'list', '--format', 'json']; |
| |
| it('gets a map of device name to FuchsiaDevice.', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| stubbedSpawn.spawnStubInfo.callsFake(function () { |
| setTimeout(() => { |
| stubbedSpawn.spawnEvent.stdout?.emit('data', JSON.stringify(targetListData)); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| }, 1); |
| return stubbedSpawn.spawnEvent; |
| }); |
| |
| try { |
| let deviceList = await ffx.getTargetList(); |
| |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, TARGET_LIST_ARGS, { cwd: TEST_CWD }]); |
| |
| // check number of devices |
| assert.strictEqual(Object.keys(deviceList).length, targetListData.length); |
| |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| }); |
| |
| it('gets a map of device name to FuchsiaDevice and gets the default.', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| stubbedSpawn.spawnStubInfo.callsFake(function () { |
| setTimeout(() => { |
| stubbedSpawn.spawnEvent.stdout?.emit('data', JSON.stringify(targetListData)); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| }, 1); |
| return stubbedSpawn.spawnEvent; |
| }); |
| |
| try { |
| let deviceList = await ffx.getTargetList(); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, TARGET_LIST_ARGS, { cwd: TEST_CWD }]); |
| |
| // check number of devices |
| assert.strictEqual(Object.keys(deviceList).length, targetListData.length); |
| |
| // check there is no default. |
| for (let name in deviceList) { |
| assert.strictEqual(deviceList[name].isDefault(), |
| deviceList[name].nodeName === 'another-device'); |
| } |
| } catch (err) { |
| assert.fail(`unexpected error: ${err}`); |
| } |
| }); |
| |
| it('Verify set target event', async () => { |
| const ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| |
| stubbedSpawn.spawnStubInfo.callsFake(function () { |
| setTimeout(() => { |
| stubbedSpawn.spawnEvent.stdout?.emit('data', JSON.stringify(targetListData)); |
| stubbedSpawn.spawnEvent.emit('exit', 0); |
| }, 1); |
| return stubbedSpawn.spawnEvent; |
| }); |
| |
| let count = 0; |
| ffx.onSetTarget(target => { |
| count += 1; |
| }); |
| |
| assert.strictEqual(count, 0); |
| ffx.setFfxPath(TEST_FFX); |
| await new Promise(resolve => setTimeout(resolve, 50)); |
| assert.strictEqual(count, 1); |
| }); |
| }); |
| |
| describe('#runLog', () => { |
| let ffx: Ffx; |
| let log: vscode.OutputChannel; |
| |
| const LOG_ARGS = ['--target', 'foo', '--machine', 'json', 'log']; |
| |
| beforeEach(() => { |
| let logger = sandbox.spy(vscode.window.createOutputChannel); |
| log = logger('test_ffx'); |
| ffx = new Ffx(log, TEST_CWD, TEST_FFX); |
| }); |
| |
| it('parses stdout and sends it to the callback', () => { |
| let received = undefined; |
| const TEST_DATA : Object = {'foo': 'bar'}; |
| stubbedSpawn.spawnStubInfo.callsFake(() => stubbedSpawn.spawnEvent); |
| ffx.runLog('foo', (data) => { |
| received = data; |
| }); |
| assert.deepStrictEqual(stubbedSpawn.spawnStubInfo.getCall(0).args, |
| [TEST_FFX, LOG_ARGS, { cwd: TEST_CWD }]); |
| stubbedSpawn.spawnEvent.stdout?.emit('data', JSON.stringify(TEST_DATA)); |
| assert.deepStrictEqual(received, TEST_DATA); |
| }); |
| |
| it('sends stderr to the log', () => { |
| sandbox.spy(log, 'appendLine'); |
| stubbedSpawn.spawnStubInfo.callsFake(() => stubbedSpawn.spawnEvent); |
| ffx.runLog('foo', (_) => {}); |
| stubbedSpawn.spawnEvent.stderr?.emit('data', 'oh no'); |
| // @ts-ignore: sinon |
| assert.ok(log.appendLine.calledTwice); |
| assert.deepStrictEqual( |
| // @ts-ignore: sinon |
| log.appendLine.getCall(0).args[0], |
| `Running: /path/to/ffx ${LOG_ARGS.join(' ')}`); |
| assert.deepStrictEqual( |
| // @ts-ignore: sinon |
| log.appendLine.getCall(1).args[0], |
| `Error [/path/to/ffx ${LOG_ARGS.join(' ')}]: oh no` |
| ); |
| }); |
| }); |
| }); |