diff --git a/extensions/resource-deployment/src/localizedConstants.ts b/extensions/resource-deployment/src/localizedConstants.ts index a8cba21524..027c3e91e6 100644 --- a/extensions/resource-deployment/src/localizedConstants.ts +++ b/extensions/resource-deployment/src/localizedConstants.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { EOL } from 'os'; import * as nls from 'vscode-nls'; +import { getErrorMessage } from './common/utils'; import { ResourceTypeCategories } from './constants'; import { FieldType, OptionsType } from './interfaces'; - const localize = nls.loadMessageBundle(); export const account = localize('azure.account', "Azure Account"); @@ -45,6 +46,10 @@ export const multipleValidationErrors = localize("validation.multipleValidationE export const scriptToNotebook = localize('ui.ScriptToNotebookButton', "Script"); export const deployNotebook = localize('ui.DeployButton', "Run"); +export const viewErrorDetail = localize('resourceDeployment.ViewErrorDetail', "View error detail"); +export const failedToOpenNotebook = (error: any) => localize('resourceDeployment.FailedToOpenNotebook', "An error occurred opening the output notebook. {1}{2}.", EOL, getErrorMessage(error)); +export const backgroundExecutionFailed = (taskName: string) => localize('resourceDeployment.BackgroundExecutionFailed', "The task \"{0}\" has failed.", taskName); +export const taskFailedWithNoOutputNotebook = (taskName: string) => localize('resourceDeployment.TaskFailedWithNoOutputNotebook', "The task \"{0}\" failed and no output Notebook was generated.", taskName); export function getResourceTypeCategoryLocalizedString(resourceTypeCategory: string): string { switch (resourceTypeCategory) { diff --git a/extensions/resource-deployment/src/services/notebookService.ts b/extensions/resource-deployment/src/services/notebookService.ts index c6c9c481f5..58de02818b 100644 --- a/extensions/resource-deployment/src/services/notebookService.ts +++ b/extensions/resource-deployment/src/services/notebookService.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; -import { EOL } from 'os'; + import * as path from 'path'; -import { isString } from 'util'; import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; -import { NotebookPathInfo } from '../interfaces'; import { getDateTimeString, getErrorMessage } from '../common/utils'; +import { NotebookPathInfo } from '../interfaces'; import { IPlatformService } from './platformService'; +import * as loc from '../localizedConstants'; const localize = nls.loadMessageBundle(); export interface Notebook { @@ -42,6 +42,7 @@ export interface INotebookService { backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookPathInfo | Notebook, tempNotebookPrefix: string, platformService: IPlatformService, env?: NodeJS.ProcessEnv): void; } + export class NotebookService implements INotebookService { constructor(private platformService: IPlatformService, private extensionPath: string) { } @@ -145,21 +146,20 @@ export class NotebookService implements INotebookService { } else { op.updateStatus(azdata.TaskStatus.Failed, result.errorMessage); if (result.outputNotebook) { - const viewErrorDetail = localize('resourceDeployment.ViewErrorDetail', "View error detail"); - const taskFailedMessage = localize('resourceDeployment.BackgroundExecutionFailed', "The task \"{0}\" has failed.", taskName); - const selectedOption = await vscode.window.showErrorMessage(taskFailedMessage, viewErrorDetail); + const taskFailedMessage = loc.backgroundExecutionFailed(taskName); + const selectedOption = await vscode.window.showErrorMessage(taskFailedMessage, loc.viewErrorDetail); platformService.logToOutputChannel(taskFailedMessage); - if (selectedOption === viewErrorDetail) { + if (selectedOption === loc.viewErrorDetail) { try { await this.openNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook); } catch (error) { - const openNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred opening the output notebook. {1}{2}.", EOL, getErrorMessage(error)); + const openNotebookError = loc.failedToOpenNotebook(error); platformService.logToOutputChannel(openNotebookError); vscode.window.showErrorMessage(openNotebookError); } } } else { - const errorMessage = localize('resourceDeployment.TaskFailedWithNoOutputNotebook', "The task \"{0}\" failed and no output Notebook was generated.", taskName); + const errorMessage = loc.taskFailedWithNoOutputNotebook(taskName); platformService.logToOutputChannel(errorMessage); vscode.window.showErrorMessage(errorMessage); } @@ -192,7 +192,7 @@ export class NotebookService implements INotebookService { */ getNotebookPath(notebook: string | NotebookPathInfo): string { let notebookPath; - if (notebook && !isString(notebook)) { + if (notebook && (typeof notebook !== 'string')) { const platform = this.platformService.platform(); if (platform === 'win32') { notebookPath = notebook.win32; diff --git a/extensions/resource-deployment/src/test/services/notebookService.test.ts b/extensions/resource-deployment/src/test/services/notebookService.test.ts index 35e859147d..f903ca341d 100644 --- a/extensions/resource-deployment/src/test/services/notebookService.test.ts +++ b/extensions/resource-deployment/src/test/services/notebookService.test.ts @@ -3,38 +3,64 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as TypeMoq from 'typemoq'; +import * as azdata from 'azdata'; import 'mocha'; -import { NotebookService } from '../../services/notebookService'; -import assert = require('assert'); +import * as path from 'path'; +import * as should from 'should'; +import * as sinon from 'sinon'; +import * as TypeMoq from 'typemoq'; +import * as vscode from 'vscode'; import { NotebookPathInfo } from '../../interfaces'; +import { Notebook, NotebookService } from '../../services/notebookService'; import { IPlatformService } from '../../services/platformService'; +import * as loc from '../../localizedConstants'; +import assert = require('assert'); +import { Deferred } from '../utils'; -describe('Notebook Service Tests', function (): void { +describe('NotebookService', function (): void { + const notebookInput = 'test-notebook.ipynb'; + const sourceNotebookContent = '{ "cells": [] }'; + const notebookFileName = 'mynotebook.ipynb'; + const expectedTargetFileName = 'mynotebook'; + const extensionPath = path.resolve(__dirname, '..', '..', '..'); + const sourceNotebookRelativePath = `./notebooks/${notebookFileName}`; + const sourceNotebookAbsolutePath = path.resolve(extensionPath, sourceNotebookRelativePath); + const notebookWin32 = 'test-notebook-win32.ipynb'; + const notebookDarwin = 'test-notebook-darwin.ipynb'; + const notebookLinux = 'test-notebook-linux.ipynb'; + const storagePath = __dirname; + let mockPlatformService: TypeMoq.IMock, notebookService: NotebookService; - it('getNotebook with string parameter', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const notebookService = new NotebookService(mockPlatformService.object, ''); - const notebookInput = 'test-notebook.ipynb'; + beforeEach('NotebookService Setup', () => { + mockPlatformService = TypeMoq.Mock.ofType(); + notebookService = new NotebookService(mockPlatformService.object, extensionPath); + }); + + afterEach('NotebookService cleanup', () => { + sinon.restore(); + }); + + it('getNotebook with string parameter', async () => { + mockPlatformService.setup(x => x.fileExists(notebookInput)).returns(async () => true); + mockPlatformService.setup(x => x.readTextFile(notebookInput)).returns(async () => sourceNotebookContent); + let returnValue = await notebookService.getNotebook(notebookInput); + returnValue.should.deepEqual(JSON.parse(sourceNotebookContent), 'returned notebook does not match expected value'); + }); + + it('getNotebookPath with string parameter', () => { mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); let returnValue = notebookService.getNotebookPath(notebookInput); - assert.equal(returnValue, notebookInput, 'returned notebook name does not match expected value'); + returnValue.should.equal(notebookInput, 'returned notebook name does not match expected value'); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); mockPlatformService.reset(); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); returnValue = notebookService.getNotebookPath(''); - assert.equal(returnValue, '', 'returned notebook name does not match expected value is not an empty string'); + returnValue.should.equal('', 'returned notebook name does not match expected value is not an empty string'); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.never()); }); it('getNotebook with NotebookInfo parameter', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const notebookService = new NotebookService(mockPlatformService.object, ''); - const notebookWin32 = 'test-notebook-win32.ipynb'; - const notebookDarwin = 'test-notebook-darwin.ipynb'; - const notebookLinux = 'test-notebook-linux.ipynb'; - const notebookInput: NotebookPathInfo = { darwin: notebookDarwin, win32: notebookWin32, @@ -42,43 +68,32 @@ describe('Notebook Service Tests', function (): void { }; mockPlatformService.setup((service) => service.platform()).returns(() => { return 'win32'; }); let returnValue = notebookService.getNotebookPath(notebookInput); - assert.equal(returnValue, notebookWin32, 'returned notebook name does not match expected value for win32 platform'); + returnValue.should.equal(notebookWin32, 'returned notebook name does not match expected value for win32 platform'); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); mockPlatformService.reset(); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'darwin'; }); returnValue = notebookService.getNotebookPath(notebookInput); - assert.equal(returnValue, notebookDarwin, 'returned notebook name does not match expected value for darwin platform'); + returnValue.should.equal(notebookDarwin, 'returned notebook name does not match expected value for darwin platform'); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); mockPlatformService.reset(); mockPlatformService.setup((service) => service.platform()).returns(() => { return 'linux'; }); returnValue = notebookService.getNotebookPath(notebookInput); - assert.equal(returnValue, notebookLinux, 'returned notebook name does not match expected value for linux platform'); + returnValue.should.equal(notebookLinux, 'returned notebook name does not match expected value for linux platform'); mockPlatformService.verify((service) => service.platform(), TypeMoq.Times.once()); }); it('findNextUntitledEditorName with no name conflict', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const notebookService = new NotebookService(mockPlatformService.object, ''); - const notebookFileName = 'mynotebook.ipynb'; - const sourceNotebookPath = `./notebooks/${notebookFileName}`; - - const expectedTargetFile = 'mynotebook'; mockPlatformService.setup((service) => service.isNotebookNameUsed(TypeMoq.It.isAnyString())) .returns((path) => { return false; }); - const actualFileName = notebookService.findNextUntitledEditorName(sourceNotebookPath); + const actualFileName = notebookService.findNextUntitledEditorName(sourceNotebookRelativePath); mockPlatformService.verify((service) => service.isNotebookNameUsed(TypeMoq.It.isAnyString()), TypeMoq.Times.once()); - assert.equal(actualFileName, expectedTargetFile, 'target file name is not correct'); + actualFileName.should.equal(expectedTargetFileName, 'target file name is not correct'); }); it('findNextUntitledEditorName with name conflicts', () => { - const mockPlatformService = TypeMoq.Mock.ofType(); - const notebookService = new NotebookService(mockPlatformService.object, ''); - const notebookFileName = 'mynotebook.ipynb'; - const sourceNotebookPath = `./notebooks/${notebookFileName}`; const expectedFileName = 'mynotebook-2'; - const expected1stAttemptTargetFile = 'mynotebook'; const expected2ndAttemptTargetFile = 'mynotebook-1'; mockPlatformService.setup((service) => service.isNotebookNameUsed(TypeMoq.It.isAnyString())) @@ -90,8 +105,177 @@ describe('Notebook Service Tests', function (): void { } return false; }); - const actualFileName = notebookService.findNextUntitledEditorName(sourceNotebookPath); + const actualFileName = notebookService.findNextUntitledEditorName(sourceNotebookRelativePath); mockPlatformService.verify((service) => service.isNotebookNameUsed(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(3)); - assert.equal(actualFileName, expectedFileName, 'target file name is not correct'); + assert.strictEqual(actualFileName, expectedFileName, 'target file name is not correct'); + }); + + it('showNotebookAsUntitled', async () => { + const { showNotebookStub } = showNotebookSetup(sourceNotebookContent); + await notebookService.showNotebookAsUntitled(sourceNotebookRelativePath); + showNotebookVerify(showNotebookStub, expectedTargetFileName, sourceNotebookContent); + }); + + describe('openNotebook', () => { + beforeEach('openNotebook setup', () => { + mockPlatformService.setup(x => x.fileExists(sourceNotebookAbsolutePath)).returns(async () => true); // fileExists returns true when called with sourceNotebookAbsolutePath + mockPlatformService.setup(x => x.fileExists(TypeMoq.It.isAnyString())).returns(async () => false); // fileExists returns false when called with any other string + }); + [sourceNotebookRelativePath, sourceNotebookAbsolutePath].forEach((notebookPath) => { + it(`notebookPath: ${notebookPath}`, async () => { + const { showNotebookStub } = showNotebookSetup(sourceNotebookContent); + await notebookService.openNotebook(notebookPath); + showNotebookVerify(showNotebookStub, expectedTargetFileName, sourceNotebookContent); + }); + }); + }); + + it('openNotebookWithEdits', async () => { + mockPlatformService.setup(x => x.fileExists(sourceNotebookAbsolutePath)).returns(async () => true); // fileExists returns true when called with sourceNotebookAbsolutePath + const editorBuilder = { + insertCell(value: azdata.nb.ICellContents, index?: number, collapsed?: boolean): void { } + }; + const editorBuilderStub = sinon.stub(editorBuilder, 'insertCell').returns(); + const { showNotebookStub, notebookEditorStub } = showNotebookSetup(sourceNotebookContent, editorBuilder); + const cellStatements: string[] = []; + await notebookService.openNotebookWithEdits(sourceNotebookAbsolutePath, cellStatements); + notebookEditorStub.callCount.should.equal(1); + editorBuilderStub.callCount.should.equal(1); + const valueInserted = editorBuilderStub.getCall(0).args[0]; + valueInserted.cell_type.should.equal('code'); + valueInserted.source.should.equal(cellStatements); + const insertionPosition = editorBuilderStub.getCall(0).args[1]; + should(insertionPosition).be.equal(0, 'default insertion point should be 0'); + showNotebookVerify(showNotebookStub, expectedTargetFileName, sourceNotebookContent); + }); + + it('openNotebookWithContent', async () => { + const title = 'title'; + const { showNotebookStub } = showNotebookSetup(sourceNotebookContent); + await notebookService.openNotebookWithContent(title, sourceNotebookContent); + showNotebookVerify(showNotebookStub, undefined, sourceNotebookContent); + }); + + describe('executeNotebook', () => { + it('success', async () => { + executeNotebookSetup({ mockPlatformService, storagePath }); + const result = await notebookService.executeNotebook({}, process.env); + result.succeeded.should.be.true(); + }); + it('fails', async () => { + const errorMessage = 'errorMessage'; + executeNotebookSetup({ mockPlatformService, storagePath, errorMessage, sourceNotebookContent }); + const result = await notebookService.executeNotebook({}, process.env); + result.succeeded.should.be.false('executeNotebook should return an object with succeeded set to false when an error occurs during execution'); + result.outputNotebook!.should.equal(sourceNotebookContent); + result.errorMessage!.should.equal(errorMessage); + }); + }); + + describe('backgroundExecuteNotebook', () => { + const taskName = 'taskName'; + const deferred = new Deferred(); + it('success', async () => { + executeNotebookSetup({ mockPlatformService, storagePath, deferred }); + const stub = sinon.stub(azdata.tasks, 'startBackgroundOperation').callThrough(); + notebookService.backgroundExecuteNotebook(taskName, { cells: [] }, 'deploy'/*tempNotebookPrefix*/, mockPlatformService.object, process.env); + verifyBackgroundExecuteNotebookKickoff(stub, taskName); + await deferred.promise; + }); + [true, false].forEach(outputProduced => { + it(`fails, with outputGenerated = ${outputProduced}`, async () => { + const deferred = new Deferred(); + const errorMessage = 'errorMessage'; + executeNotebookSetup({ mockPlatformService, storagePath, errorMessage, sourceNotebookContent: outputProduced ? sourceNotebookContent : '' }); + const stub = sinon.stub(azdata.tasks, 'startBackgroundOperation').callThrough(); + sinon.stub(azdata.nb, 'showNotebookDocument').rejects(new Error(errorMessage)); + sinon.stub(vscode.window, 'showErrorMessage').callsFake(async (message, ...items) => { + if (outputProduced) { + if (items?.length === 1) { + message.should.equal(loc.backgroundExecutionFailed(taskName)); + return loc.viewErrorDetail; + } else { + message.should.equal(loc.failedToOpenNotebook(errorMessage)); + deferred.resolve(); + } + } else { + message.should.equal(loc.taskFailedWithNoOutputNotebook(taskName)); + deferred.resolve(); + } + }); + notebookService.backgroundExecuteNotebook(taskName, { cells: [] }, 'deploy'/*tempNotebookPrefix*/, mockPlatformService.object, process.env); + verifyBackgroundExecuteNotebookKickoff(stub, taskName); + await deferred.promise; + }); + }); + }); }); + +function verifyBackgroundExecuteNotebookKickoff(stub: sinon.SinonStub, taskName: string) { + const operationInfo = stub.getCall(0).args[0]; + stub.callCount.should.equal(1); + operationInfo.displayName.should.equal(taskName); + operationInfo.description.should.equal(taskName); + should(operationInfo.operation).not.be.undefined(); + operationInfo.isCancelable.should.be.false(); +} + +function executeNotebookSetup({ mockPlatformService, storagePath, deferred, errorMessage, sourceNotebookContent }: { mockPlatformService: TypeMoq.IMock; storagePath: string; deferred?: Deferred, errorMessage?: string; sourceNotebookContent?: string; }) { + mockPlatformService.setup(x => x.storagePath()).returns(() => storagePath); + mockPlatformService.setup(x => x.saveTextFile(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())); + if (errorMessage) { + mockPlatformService.setup(x => x.runCommand( + TypeMoq.It.is((s: string) => s.startsWith('azdata notebook run')), + TypeMoq.It.isAny() + )).throws(new Error(errorMessage)); + } else { + mockPlatformService.setup(x => x.runCommand( + TypeMoq.It.is((s: string) => s.startsWith('azdata notebook run')), + TypeMoq.It.isAny() + )).callback(async () => { + if (deferred) { + deferred.resolve(); + } + return 'success'; + }); + } + mockPlatformService.setup(x => x.deleteFile(TypeMoq.It.is((s: string) => path.dirname(s) === storagePath && path.basename(s).startsWith('nb-')))) + .returns(async () => { }); // match deletion of executed notebookPath + mockPlatformService.setup(x => x.deleteFile(TypeMoq.It.is((s: string) => path.dirname(s) === storagePath && path.basename(s).startsWith('output-nb-')))) + .returns(async () => { }); // match deletion of output of executed notebookPath + if (errorMessage) { + mockPlatformService.setup(x => x.fileExists(TypeMoq.It.is((s: string) => path.dirname(s) === storagePath && path.basename(s).startsWith('output-nb-')))) + .returns(async () => true); + mockPlatformService.setup(x => x.readTextFile(TypeMoq.It.is((s: string) => path.dirname(s) === storagePath && path.basename(s).startsWith('output-nb-')))) + .returns(async () => sourceNotebookContent!); + } +} + +function showNotebookVerify(stub: sinon.SinonStub, expectedTargetFileName: string | undefined, sourceNotebookContent: string) { + stub.callCount.should.equal(1); + const untitledFileName = stub.getCall(0).args[0]!; + untitledFileName.scheme.should.equal('untitled'); + if (expectedTargetFileName) { + untitledFileName.path.should.equal(expectedTargetFileName); + } + const options = stub.getCall(0).args[1]!; + options.initialContent!.should.equal(sourceNotebookContent); +} + +function showNotebookSetup(sourceNotebookContent: string, editorBuilder?: azdata.nb.NotebookEditorEdit) { + sinon.stub(vscode.workspace, 'openTextDocument').resolves({ getText: () => sourceNotebookContent }); + const nbEditor = { + edit(callback: (editBuilder: azdata.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable { + return Promise.resolve(true); + } + }; + const notebookEditorStub = sinon.stub(nbEditor, 'edit').callsFake(async callback => { + if (editorBuilder) { + callback(editorBuilder); + } + return true; + }); + const showNotebookStub = sinon.stub(azdata.nb, 'showNotebookDocument').resolves(nbEditor); + return { showNotebookStub, notebookEditorStub }; +} diff --git a/extensions/resource-deployment/src/test/stubs.ts b/extensions/resource-deployment/src/test/stubs.ts new file mode 100644 index 0000000000..922561585c --- /dev/null +++ b/extensions/resource-deployment/src/test/stubs.ts @@ -0,0 +1,105 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as events from 'events'; +import * as cp from 'promisify-child-process'; +import { Readable } from 'stream'; + +export class TestChildProcessPromise implements cp.ChildProcessPromise { + private _promise: Promise; + private _event: events.EventEmitter = new events.EventEmitter(); + + constructor() { + this._promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } + resolve!: (value?: T | PromiseLike) => void; + reject!: (reason?: any) => void; + then(onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, onRejected?: ((reason: any) => TResult2 | PromiseLike) | null): Promise { + return this._promise.then(onFulfilled, onRejected); + } + catch(onRejected?: ((reason: any) => TResult | PromiseLike) | null): Promise { + return this._promise.catch(onRejected); + } + [Symbol.toStringTag]: string; + finally(onFinally?: (() => void) | null): Promise { + return this._promise.finally(onFinally); + } + stdin: any = this._event; + stdout: Readable | null = this._event; + stderr: Readable | null = this._event; + channel?: any; + stdio: [any, Readable | null, Readable | null, any, any] = [this.stdin, this.stdout, this.stderr, undefined, undefined]; + killed: boolean = false; + pid: number = -1; + connected: boolean = false; + kill(signal?: number | 'SIGABRT' | 'SIGALRM' | 'SIGBUS' | 'SIGCHLD' | 'SIGCONT' | 'SIGFPE' | 'SIGHUP' | 'SIGILL' | 'SIGINT' | 'SIGIO' | 'SIGIOT' | 'SIGKILL' | 'SIGPIPE' | 'SIGPOLL' | 'SIGPROF' | 'SIGPWR' | 'SIGQUIT' | 'SIGSEGV' | 'SIGSTKFLT' | 'SIGSTOP' | 'SIGSYS' | 'SIGTERM' | 'SIGTRAP' | 'SIGTSTP' | 'SIGTTIN' | 'SIGTTOU' | 'SIGUNUSED' | 'SIGURG' | 'SIGUSR1' | 'SIGUSR2' | 'SIGVTALRM' | 'SIGWINCH' | 'SIGXCPU' | 'SIGXFSZ' | 'SIGBREAK' | 'SIGLOST' | 'SIGINFO'): void { + throw new Error('Method not implemented.'); + } + + send(message: any, callback?: (error: Error | null) => void): boolean; + send(message: any, sendHandle?: any, callback?: (error: Error | null) => void): boolean; + send(message: any, sendHandle?: any, options?: any, callback?: (error: Error | null) => void): boolean; + send(message: any, sendHandle?: any, options?: any, callback?: any): boolean { + throw new Error('Method not implemented.'); + } + disconnect(): void { + throw new Error('Method not implemented.'); + } + unref(): void { + throw new Error('Method not implemented.'); + } + ref(): void { + throw new Error('Method not implemented.'); + } + addListener(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + on(event: string | symbol, listener: (...args: any[]) => void): this { + this._event.on(event, listener); + return this; + } + once(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + prependListener(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + removeListener(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + off(event: string | symbol, listener: (...args: any[]) => void): this { + throw new Error('Method not implemented.'); + } + removeAllListeners(event?: string | symbol): this { + throw new Error('Method not implemented.'); + } + setMaxListeners(n: number): this { + throw new Error('Method not implemented.'); + } + getMaxListeners(): number { + throw new Error('Method not implemented.'); + } + listeners(event: string | symbol): Function[] { + throw new Error('Method not implemented.'); + } + rawListeners(event: string | symbol): Function[] { + throw new Error('Method not implemented.'); + } + emit(event: string | symbol, ...args: any[]): boolean { + return this._event.emit(event, args); + } + eventNames(): (string | symbol)[] { + throw new Error('Method not implemented.'); + } + listenerCount(type: string | symbol): number { + throw new Error('Method not implemented.'); + } +} diff --git a/extensions/resource-deployment/src/test/utils.ts b/extensions/resource-deployment/src/test/utils.ts new file mode 100644 index 0000000000..61dbae9f1b --- /dev/null +++ b/extensions/resource-deployment/src/test/utils.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export class Deferred { + promise: Promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + });; + resolve!: (value?: T | PromiseLike) => void; + reject!: (reason?: any) => void; +}