diff --git a/extension.bundle.ts b/extension.bundle.ts index 9dd8f87d91..8df1e65369 100644 --- a/extension.bundle.ts +++ b/extension.bundle.ts @@ -31,6 +31,7 @@ export { LineSplitter } from './src/debugging/coreclr/lineSplitter'; export { delay } from './src/utils/delay'; export { Lazy, AsyncLazy } from './src/utils/lazy'; export { OSProvider } from './src/utils/LocalOSProvider'; +export { bufferToString } from './src/utils/spawnAsync'; export { DockerDaemonIsLinuxPrerequisite, DockerfileExistsPrerequisite, DotNetSdkInstalledPrerequisite, LinuxUserInDockerGroupPrerequisite, MacNuGetFallbackFolderSharedPrerequisite } from './src/debugging/coreclr/prereqManager'; export { ext } from './src/extensionVariables'; export { globAsync } from './src/utils/globAsync'; @@ -40,7 +41,6 @@ export { inferCommand, inferPackageName, InspectMode, NodePackage } from './src/ export { nonNullProp } from './src/utils/nonNull'; export { getDockerOSType, isWindows10RS3OrNewer, isWindows10RS4OrNewer, isWindows10RS5OrNewer, isWindows1019H1OrNewer } from "./src/utils/osUtils"; export { Platform, PlatformOS } from './src/utils/platform'; -export { DefaultTerminalProvider } from './src/utils/TerminalProvider'; export { trimWithElipsis } from './src/utils/trimWithElipsis'; export { recursiveFindTaskByType } from './src/tasks/TaskHelper'; export { TaskDefinitionBase } from './src/tasks/TaskDefinitionBase'; diff --git a/src/commands/compose.ts b/src/commands/compose.ts index 2e907bf8ad..6264800d16 100644 --- a/src/commands/compose.ts +++ b/src/commands/compose.ts @@ -5,8 +5,8 @@ import * as vscode from 'vscode'; import { IActionContext } from 'vscode-azureextensionui'; -import { ext } from '../extensionVariables'; import { localize } from "../localize"; +import { executeAsTask } from '../utils/executeAsTask'; import { createFileItem, Item, quickPickDockerComposeFileItem } from '../utils/quickPickFile'; import { quickPickWorkspaceFolder } from '../utils/quickPickWorkspaceFolder'; import { selectComposeCommand } from './selectCommandTemplate'; @@ -30,12 +30,10 @@ async function compose(context: IActionContext, commands: ('up' | 'down')[], mes selectedItems = selectedItem ? [selectedItem] : []; } - const terminal: vscode.Terminal = ext.terminalProvider.createTerminal('Docker Compose'); const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker'); const build: boolean = configOptions.get('dockerComposeBuild', true); const detached: boolean = configOptions.get('dockerComposeDetached', true); - terminal.sendText(`cd "${folder.uri.fsPath}"`); for (const command of commands) { if (selectedItems.length === 0) { const terminalCommand = await selectComposeCommand( @@ -46,7 +44,7 @@ async function compose(context: IActionContext, commands: ('up' | 'down')[], mes detached, build ); - terminal.sendText(terminalCommand); + await executeAsTask(context, terminalCommand, 'Docker Compose', /* addDockerEnv: */ true, folder); } else { for (const item of selectedItems) { const terminalCommand = await selectComposeCommand( @@ -57,10 +55,9 @@ async function compose(context: IActionContext, commands: ('up' | 'down')[], mes detached, build ); - terminal.sendText(terminalCommand); + await executeAsTask(context, terminalCommand, 'Docker Compose', /* addDockerEnv: */ true, folder); } } - terminal.show(); } } diff --git a/src/commands/containers/attachShellContainer.ts b/src/commands/containers/attachShellContainer.ts index 4fee74d3ad..a5c8b54bc8 100644 --- a/src/commands/containers/attachShellContainer.ts +++ b/src/commands/containers/attachShellContainer.ts @@ -8,6 +8,7 @@ import { IActionContext } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; +import { executeAsTask } from '../../utils/executeAsTask'; import { getDockerOSType } from '../../utils/osUtils'; import { selectAttachCommand } from '../selectCommandTemplate'; @@ -39,7 +40,5 @@ export async function attachShellContainer(context: IActionContext, node?: Conta shellCommand ); - const terminal = ext.terminalProvider.createTerminal(`Shell: ${node.containerName}`); - terminal.sendText(terminalCommand); - terminal.show(); + await executeAsTask(context, terminalCommand, `Shell: ${node.containerName}`, /* addDockerEnv: */ true); } diff --git a/src/commands/containers/viewContainerLogs.ts b/src/commands/containers/viewContainerLogs.ts index 5ec713a6a3..38b0955224 100644 --- a/src/commands/containers/viewContainerLogs.ts +++ b/src/commands/containers/viewContainerLogs.ts @@ -7,6 +7,7 @@ import { IActionContext } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { ContainerTreeItem } from '../../tree/containers/ContainerTreeItem'; +import { executeAsTask } from '../../utils/executeAsTask'; import { selectLogsCommand } from '../selectCommandTemplate'; export async function viewContainerLogs(context: IActionContext, node?: ContainerTreeItem): Promise { @@ -24,7 +25,5 @@ export async function viewContainerLogs(context: IActionContext, node?: Containe node.containerId ); - const terminal = ext.terminalProvider.createTerminal(node.fullTag); - terminal.sendText(terminalCommand); - terminal.show(); + await executeAsTask(context, terminalCommand, node.fullTag, /* addDockerEnv: */ true); } diff --git a/src/commands/dockerInstaller.ts b/src/commands/dockerInstaller.ts index 6b17f4cd79..b620425915 100644 --- a/src/commands/dockerInstaller.ts +++ b/src/commands/dockerInstaller.ts @@ -4,11 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import { IActionContext } from 'vscode-azureextensionui'; import ChildProcessProvider from '../debugging/coreclr/ChildProcessProvider'; import { LocalFileSystemProvider } from '../debugging/coreclr/fsProvider'; import { OSTempFileProvider } from '../debugging/coreclr/tempFileProvider'; import { ext } from '../extensionVariables'; import { localize } from '../localize'; +import { executeAsTask } from '../utils/executeAsTask'; import { streamToFile } from '../utils/httpRequest'; import LocalOSProvider from '../utils/LocalOSProvider'; import { execAsync } from '../utils/spawnAsync'; @@ -18,7 +20,7 @@ export abstract class DockerInstallerBase { protected abstract fileExtension: string; protected abstract getInstallCommand(fileName: string): string; - public async downloadAndInstallDocker(): Promise { + public async downloadAndInstallDocker(context: IActionContext): Promise { const confirmInstall: string = localize('vscode-docker.commands.DockerInstallerBase.confirm', 'Are you sure you want to install Docker on this machine?'); const installTitle: string = localize('vscode-docker.commands.DockerInstallerBase.install', 'Install'); const downloadingMessage: string = localize('vscode-docker.commands.DockerInstallerBase.downloading', 'Downloading Docker installer...'); @@ -34,7 +36,7 @@ export abstract class DockerInstallerBase { const command = this.getInstallCommand(downloadedFileName); // eslint-disable-next-line @typescript-eslint/no-floating-promises vscode.window.showInformationMessage(installationMessage); - await this.install(downloadedFileName, command); + await this.install(context, downloadedFileName, command); } private async downloadInstaller(): Promise { @@ -46,7 +48,7 @@ export abstract class DockerInstallerBase { return fileName; } - protected abstract install(fileName: string, cmd: string): Promise; + protected abstract install(context: IActionContext, fileName: string, cmd: string): Promise; } export class WindowsDockerInstaller extends DockerInstallerBase { @@ -57,7 +59,7 @@ export class WindowsDockerInstaller extends DockerInstallerBase { return `"${fileName}"`; } - protected async install(fileName: string, cmd: string): Promise { + protected async install(context: IActionContext, fileName: string, cmd: string): Promise { const fsProvider = new LocalFileSystemProvider(); try { ext.outputChannel.appendLine(localize('vscode-docker.commands.DockerInstallerBase.downloadCompleteMessage', 'Executing command {0}', cmd)); @@ -77,10 +79,10 @@ export class MacDockerInstaller extends DockerInstallerBase { return `chmod +x '${fileName}' && open '${fileName}'`; } - protected async install(fileName: string): Promise { - const terminal = ext.terminalProvider.createTerminal(localize('vscode-docker.commands.MacDockerInstaller.terminalTitle', 'Docker Install')); + protected async install(context: IActionContext, fileName: string): Promise { + const title = localize('vscode-docker.commands.MacDockerInstaller.terminalTitle', 'Docker Install'); const command = this.getInstallCommand(fileName); - terminal.sendText(command); - terminal.show(); + + await executeAsTask(context, command, title); } } diff --git a/src/commands/images/buildImage.ts b/src/commands/images/buildImage.ts index 62b3572a8c..5d77e57d3b 100644 --- a/src/commands/images/buildImage.ts +++ b/src/commands/images/buildImage.ts @@ -10,6 +10,7 @@ import { ext } from "../../extensionVariables"; import { localize } from '../../localize'; import { getOfficialBuildTaskForDockerfile } from "../../tasks/TaskHelper"; import { delay } from "../../utils/delay"; +import { executeAsTask } from "../../utils/executeAsTask"; import { getValidImageName } from "../../utils/getValidImageName"; import { quickPickDockerFileItem } from "../../utils/quickPickFile"; import { quickPickWorkspaceFolder } from "../../utils/quickPickWorkspaceFolder"; @@ -64,8 +65,6 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode. terminalCommand = terminalCommand.replace(tagRegex, imageName); } - const terminal: vscode.Terminal = ext.terminalProvider.createTerminal('Docker'); - terminal.sendText(terminalCommand); - terminal.show(); + await executeAsTask(context, terminalCommand, 'Docker', /* addDockerEnv: */ true, rootFolder); } } diff --git a/src/commands/images/pushImage.ts b/src/commands/images/pushImage.ts index 718dbaafb8..0f6c81a4ab 100644 --- a/src/commands/images/pushImage.ts +++ b/src/commands/images/pushImage.ts @@ -10,6 +10,7 @@ import { localize } from '../../localize'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; +import { executeAsTask } from '../../utils/executeAsTask'; import { addImageTaggingTelemetry, tagImage } from './tagImage'; export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise { @@ -64,9 +65,7 @@ export async function pushImage(context: IActionContext, node: ImageTreeItem | u addImageTaggingTelemetry(context, finalTag, ''); // Finally push the image - const terminal = ext.terminalProvider.createTerminal(finalTag); - terminal.sendText(`docker push ${finalTag}`); - terminal.show(); + await executeAsTask(context, `docker push ${finalTag}`, finalTag, /* addDockerEnv: */ true); } async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise { diff --git a/src/commands/images/runAzureCliImage.ts b/src/commands/images/runAzureCliImage.ts index 50d87b0fc7..372e105581 100644 --- a/src/commands/images/runAzureCliImage.ts +++ b/src/commands/images/runAzureCliImage.ts @@ -7,8 +7,8 @@ import * as fse from 'fs-extra'; import * as os from 'os'; import * as vscode from 'vscode'; import { DialogResponses, IActionContext } from 'vscode-azureextensionui'; -import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; +import { executeAsTask } from '../../utils/executeAsTask'; import { openExternal } from '../../utils/openExternal'; import { getDockerOSType } from '../../utils/osUtils'; @@ -38,9 +38,6 @@ export async function runAzureCliImage(context: IActionContext): Promise { vol += ` -v ${homeDir}/.kube:/root/.kube`; } - const cmd: string = `docker run ${option} ${vol.trim()} -it --rm azuresdk/azure-cli-python:latest`; - const terminal: vscode.Terminal = ext.terminalProvider.createTerminal('Azure CLI'); - terminal.sendText(cmd); - terminal.show(); + await executeAsTask(context, `docker run ${option} ${vol.trim()} -it --rm azuresdk/azure-cli-python:latest`, 'Azure CLI', /* addDockerEnv: */ true); } } diff --git a/src/commands/images/runImage.ts b/src/commands/images/runImage.ts index cb591bf63b..192872315e 100644 --- a/src/commands/images/runImage.ts +++ b/src/commands/images/runImage.ts @@ -8,6 +8,7 @@ import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; import { ImageTreeItem } from '../../tree/images/ImageTreeItem'; import { callDockerode } from '../../utils/callDockerode'; +import { executeAsTask } from '../../utils/executeAsTask'; import { selectRunCommand } from '../selectCommandTemplate'; export async function runImage(context: IActionContext, node?: ImageTreeItem): Promise { @@ -36,7 +37,5 @@ async function runImageCore(context: IActionContext, node: ImageTreeItem | undef inspectInfo?.Config?.ExposedPorts ); - const terminal = ext.terminalProvider.createTerminal(node.fullTag); - terminal.sendText(terminalCommand); - terminal.show(); + await executeAsTask(context, terminalCommand, node.fullTag, /* addDockerEnv: */ true); } diff --git a/src/commands/installDocker.ts b/src/commands/installDocker.ts index 1bc58eb1c4..8fe0d5d0c6 100644 --- a/src/commands/installDocker.ts +++ b/src/commands/installDocker.ts @@ -10,9 +10,9 @@ import { MacDockerInstaller, WindowsDockerInstaller } from './dockerInstaller'; export async function installDocker(context: IActionContext): Promise { if (os.platform() === 'win32') { - await (new WindowsDockerInstaller()).downloadAndInstallDocker(); + await (new WindowsDockerInstaller()).downloadAndInstallDocker(context); } else if (os.platform() === 'darwin') { - await (new MacDockerInstaller()).downloadAndInstallDocker(); + await (new MacDockerInstaller()).downloadAndInstallDocker(context); } else { await openExternal('https://aka.ms/download-docker-linux-vscode'); } diff --git a/src/commands/registries/logOutOfDockerCli.ts b/src/commands/registries/logOutOfDockerCli.ts index 4c7e402039..f61b532563 100644 --- a/src/commands/registries/logOutOfDockerCli.ts +++ b/src/commands/registries/logOutOfDockerCli.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal } from 'vscode'; import { IActionContext } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; import { registryExpectedContextValues } from '../../tree/registries/registryContextValues'; import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; +import { executeAsTask } from '../../utils/executeAsTask'; export async function logOutOfDockerCli(context: IActionContext, node?: RegistryTreeItemBase): Promise { if (!node) { @@ -15,7 +15,5 @@ export async function logOutOfDockerCli(context: IActionContext, node?: Registry } let creds = await node.getDockerCliCredentials(); - const terminal: Terminal = ext.terminalProvider.createTerminal('docker logout'); - terminal.sendText(`docker logout ${creds.registryPath}`); - terminal.show(); + await executeAsTask(context, `docker logout ${creds.registryPath}`, 'Docker', /* addDockerEnv: */ true); } diff --git a/src/commands/registries/pullImages.ts b/src/commands/registries/pullImages.ts index aba94979b7..e1aa6aa160 100644 --- a/src/commands/registries/pullImages.ts +++ b/src/commands/registries/pullImages.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Terminal } from "vscode"; import { IActionContext } from "vscode-azureextensionui"; import { ext } from '../../extensionVariables'; import { registryExpectedContextValues } from "../../tree/registries/registryContextValues"; import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase'; import { RemoteRepositoryTreeItemBase } from "../../tree/registries/RemoteRepositoryTreeItemBase"; import { RemoteTagTreeItem } from '../../tree/registries/RemoteTagTreeItem'; +import { executeAsTask } from "../../utils/executeAsTask"; import { logInToDockerCli } from "./logInToDockerCli"; export async function pullRepository(context: IActionContext, node?: RemoteRepositoryTreeItemBase): Promise { @@ -31,7 +31,5 @@ export async function pullImage(context: IActionContext, node?: RemoteTagTreeIte async function pullImages(context: IActionContext, node: RegistryTreeItemBase, imageRequest: string): Promise { await logInToDockerCli(context, node); - const terminal: Terminal = ext.terminalProvider.createTerminal("docker pull"); - terminal.show(); - terminal.sendText(`docker pull ${node.baseImagePath}/${imageRequest}`); + await executeAsTask(context, `docker pull ${node.baseImagePath}/${imageRequest}`, 'Docker', /* addDockerEnv: */ true); } diff --git a/src/extension.ts b/src/extension.ts index bcf16937aa..d49aea9984 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -32,7 +32,6 @@ import { AzureAccountExtensionListener } from './utils/AzureAccountExtensionList import { Keytar } from './utils/keytar'; import { refreshDockerode } from './utils/refreshDockerode'; import { bufferToString } from './utils/spawnAsync'; -import { DefaultTerminalProvider } from './utils/TerminalProvider'; export type KeyInfo = { [keyName: string]: string }; @@ -58,10 +57,6 @@ function initializeExtensionVariables(ctx: vscode.ExtensionContext): void { ext.outputChannel = createAzExtOutputChannel('Docker', ext.prefix); ctx.subscriptions.push(ext.outputChannel); - if (!ext.terminalProvider) { - ext.terminalProvider = new DefaultTerminalProvider(); - } - const publisher = new TelemetryPublisher(); ctx.subscriptions.push(publisher); diff --git a/src/extensionVariables.ts b/src/extensionVariables.ts index 721437ff99..218a339426 100644 --- a/src/extensionVariables.ts +++ b/src/extensionVariables.ts @@ -13,7 +13,6 @@ import { NetworksTreeItem } from './tree/networks/NetworksTreeItem'; import { RegistriesTreeItem } from './tree/registries/RegistriesTreeItem'; import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem'; import { IKeytar } from './utils/keytar'; -import { ITerminalProvider } from "./utils/TerminalProvider"; /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -24,7 +23,6 @@ export namespace ext { export let outputChannel: IAzExtOutputChannel; export let ui: IAzureUserInput; export let reporter: ITelemetryReporter; - export let terminalProvider: ITerminalProvider; export let keytar: IKeytar | undefined; export let dockerode: Dockerode; export let dockerodeInitError: unknown; diff --git a/src/utils/TerminalProvider.ts b/src/utils/TerminalProvider.ts deleted file mode 100644 index 50bd7b5e4e..0000000000 --- a/src/utils/TerminalProvider.ts +++ /dev/null @@ -1,22 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as vscode from 'vscode'; -import { Terminal } from 'vscode'; -import { addDockerSettingsToEnv } from './addDockerSettingsToEnv'; - -export interface ITerminalProvider { - createTerminal(name: string): Terminal; -} - -export class DefaultTerminalProvider { - public createTerminal(name: string): Terminal { - let terminalOptions: vscode.TerminalOptions = {}; - terminalOptions.name = name; - terminalOptions.env = {}; - addDockerSettingsToEnv(terminalOptions.env, process.env); - return vscode.window.createTerminal(terminalOptions); - } -} diff --git a/src/utils/executeAsTask.ts b/src/utils/executeAsTask.ts new file mode 100644 index 0000000000..0762107079 --- /dev/null +++ b/src/utils/executeAsTask.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { IActionContext } from 'vscode-azureextensionui'; +import { addDockerSettingsToEnv } from './addDockerSettingsToEnv'; + +export async function executeAsTask(context: IActionContext, command: string, name: string, addDockerEnv?: boolean, workspaceFolder?: vscode.WorkspaceFolder, cwd?: string): Promise { + let newEnv: { [key: string]: string } | undefined; + + if (addDockerEnv) { + // We don't need to merge process.env into newEnv, since ShellExecution does that automatically via ShellExecutionOptions + newEnv = {}; + addDockerSettingsToEnv(newEnv, process.env); + } + + const task = new vscode.Task( + { type: 'shell' }, + workspaceFolder ?? vscode.TaskScope.Workspace, + name, + 'Docker', + new vscode.ShellExecution(command, { cwd: cwd || workspaceFolder.uri.fsPath, env: newEnv }), + [] // problemMatchers + ); + + return vscode.tasks.executeTask(task); +} diff --git a/test/TestTerminalProvider.ts b/test/TestTerminalProvider.ts deleted file mode 100644 index a433d5b34f..0000000000 --- a/test/TestTerminalProvider.ts +++ /dev/null @@ -1,204 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See LICENSE.md in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as fse from 'fs-extra'; -import * as os from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Terminal } from 'vscode'; -import { DefaultTerminalProvider } from '../extension.bundle'; - -export interface ITerminalProvider { - createTerminal(name: string): Terminal; -} - -/** - * Creates terminals for testing that automatically save the standard and error output of the commands sent to it - */ -export class TestTerminalProvider { - private _currentTerminal: TestTerminal | undefined; - - public createTerminal(name: string): TestTerminal { - let terminal = new DefaultTerminalProvider().createTerminal(name); - let testTerminal = new TestTerminal(terminal); - this._currentTerminal = testTerminal; - return testTerminal; - } - - public get currentTerminal(): TestTerminal | undefined { - return this._currentTerminal; - } -} - -class TestTerminal implements vscode.Terminal { - private static _lastSuffix: number = 1; - - private _outputFilePath: string; - private _errFilePath: string; - private _semaphorePath: string; - private _suffix: number; - private _disposed: boolean = false; - - readonly dimensions = undefined; - - constructor(private _terminal: vscode.Terminal) { - let root = (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0 && vscode.workspace.workspaceFolders[0].uri.fsPath) || os.tmpdir(); - this._suffix = TestTerminal._lastSuffix++; - - this._outputFilePath = path.join(root, `.out${this._suffix}`); - this._errFilePath = path.join(root, `.err${this._suffix}`); - this._semaphorePath = path.join(root, `.sem${this._suffix}`); - } - - /** - * Causes the terminal to exit after completing the current commands, and returns the - * redirected standard and error output. - */ - public async exit(): Promise<{ errorText: string, outputText: string }> { - this.ensureNotDisposed(); - let results = await this.waitForCompletion(); - this.hide(); - this.dispose(); - return results; - } - - /** - * Causes the terminal to wait for completion of the current commands, and returns the - * redirected standard and error output since the last call. - */ - public async waitForCompletion(): Promise<{ errorText: string, outputText: string }> { - return this.waitForCompletionCore(); - } - - private async waitForCompletionCore(options: { ignoreErrors?: boolean } = {}): Promise<{ errorText: string, outputText: string }> { - this.ensureNotDisposed(); - console.log('Waiting for terminal command completion...'); - - // Output text to a semaphore file. This will execute when the terminal is no longer busy. - this.sendTextRaw(`echo Done > ${this._semaphorePath}`); - - // Wait for the semaphore file - await this.waitForFileCreation(this._semaphorePath); - - assert(await fse.pathExists(this._outputFilePath), 'The output file from the command was not created. Sometimes this can mean the command to execute was not found.'); - let outputText = bufferToString(await fse.readFile(this._outputFilePath)); - - assert(await fse.pathExists(this._errFilePath), 'The error file from the command was not created.'); - let errorText = bufferToString(await fse.readFile(this._errFilePath)); - - console.log("OUTPUT:"); - console.log(outputText ? outputText : '(NONE)'); - console.log("END OF OUTPUT"); - - if (errorText) { - if (options.ignoreErrors) { - // console.log("ERROR OUTPUT (IGNORED):"); - // console.log(errorText.replace(/\r/, "\rIGNORED: ")); - // console.log("END OF ERROR OUTPUT (IGNORED)"); - } else { - console.log("ERRORS:"); - console.log(errorText.replace(/\r/, "\rERROR: ")); - console.log("END OF ERRORS"); - } - } - - // Remove files in preparation for next commands, if any - await fse.remove(this._semaphorePath); - await fse.remove(this._outputFilePath); - await fse.remove(this._errFilePath); - - return { outputText: outputText, errorText: errorText }; - } - - /** - * Executes one or more commands and waits for them to complete. Returns stdout output and - * throws if there is output to stdout. - */ - public async execute(commands: string | string[], options: { ignoreErrors?: boolean } = {}): Promise { - if (typeof commands === 'string') { - commands = [commands]; - } - - this.show(); - for (let command of commands) { - this.sendText(command); - } - - let results = await this.waitForCompletionCore(options); - - if (!options.ignoreErrors) { - assert.equal(results.errorText, '', `Encountered errors executing in terminal`); - } - - return results.outputText; - } - - public get name(): string { - this.ensureNotDisposed(); return this._terminal.name; - } - - public get processId(): Thenable { - this.ensureNotDisposed(); - return this._terminal.processId; - } - - private async waitForFileCreation(filePath: string): Promise { - return new Promise((resolve, _reject) => { - let timer = setInterval( - () => { - if (fse.existsSync(filePath)) { - clearInterval(timer); - resolve(); - } - }, 500); - }); - } - - /** - * Sends text to the terminal, does not wait for completion - */ - public sendText(text: string, addNewLine?: boolean): void { - this.ensureNotDisposed(); - console.log(`Executing in terminal: ${text}`); - if (addNewLine !== false) { - // Redirect the output and error output to files (not a perfect solution, but it works) - text += ` >>${this._outputFilePath} 2>>${this._errFilePath}`; - } - this.sendTextRaw(text, addNewLine); - } - - private sendTextRaw(text: string, addNewLine?: boolean): void { - this._terminal.sendText(text, addNewLine); - } - - public show(preserveFocus?: boolean): void { - this.ensureNotDisposed(); - this._terminal.show(preserveFocus); - } - - public hide(): void { - this.ensureNotDisposed(); - this._terminal.hide(); - } - - public dispose(): void { - this._disposed = true; - this._terminal.dispose(); - } - - private ensureNotDisposed(): void { - assert(!this._disposed, 'Terminal has already been disposed.'); - } -} - -function bufferToString(buffer: Buffer): string { - if (buffer.length > 2 && buffer[0] === 0xff && buffer[1] === 0xfe) { - // Buffer is in UTF-16 format (happens in some shells) - return buffer.toString("utf-16le"); - } - - return buffer.toString(); -} diff --git a/test/buildAndRun.test.ts b/test/buildAndRun.test.ts index 79b85e5cab..a07c2ac9ff 100644 --- a/test/buildAndRun.test.ts +++ b/test/buildAndRun.test.ts @@ -5,19 +5,19 @@ // The module 'assert' provides assertion methods from node import * as AdmZip from 'adm-zip'; -import * as assert from 'assert'; import * as fse from 'fs-extra'; import { Context, Suite } from 'mocha'; import * as path from 'path'; -import { commands, Uri } from 'vscode'; -import { IActionContext, createAzExtOutputChannel, IAzExtOutputChannel } from 'vscode-azureextensionui'; -import { configure, ext, httpsRequestBinary, Platform } from '../extension.bundle'; +import { commands, tasks, Uri } from 'vscode'; +import { IActionContext } from 'vscode-azureextensionui'; +import { configure, httpsRequestBinary, Platform, bufferToString } from '../extension.bundle'; import * as assertEx from './assertEx'; import { shouldSkipDockerTest } from './dockerInfo'; import { getTestRootFolder, testInEmptyFolder, testUserInput } from './global.test'; -import { TestTerminalProvider } from './TestTerminalProvider'; +import { runWithSetting } from './runWithSetting'; let testRootFolder: string = getTestRootFolder(); +let buildOutputIndex: number = 0; /** * Downloads and then extracts only a specific folder and its subfolders. @@ -62,20 +62,17 @@ async function extractFolderTo(zip: AdmZip, sourceFolderInZip: string, outputFol suite("Build Image", function (this: Suite): void { this.timeout(2 * 60 * 1000); - const outputChannel: IAzExtOutputChannel = createAzExtOutputChannel('Docker extension tests', 'docker'); - ext.outputChannel = outputChannel; - async function testConfigureAndBuildImage( platform: Platform, configureInputs: (string | undefined)[], buildInputs: (string | undefined)[] ): Promise { + const testOutputFile = path.join(testRootFolder, `buildoutput${buildOutputIndex++}.txt`); + // Set up simulated user input configureInputs.unshift(platform); - let testTerminalProvider = new TestTerminalProvider(); - ext.terminalProvider = testTerminalProvider; - let context: IActionContext = { + const context: IActionContext = { telemetry: { properties: {}, measurements: {} }, errorHandling: { issueProperties: {} } }; @@ -85,16 +82,34 @@ suite("Build Image", function (this: Suite): void { }); // Build image - let dockerFile = Uri.file(path.join(testRootFolder, 'Dockerfile')); - await testUserInput.runWithInputs(buildInputs, async () => { - await commands.executeCommand('vscode-docker.images.build', dockerFile); - }); - - let { outputText, errorText } = await testTerminalProvider.currentTerminal!.exit(); - - assert.equal(errorText, '', 'Expected no errors from Build Image'); - assertEx.assertContains(outputText, 'Successfully built'); - assertEx.assertContains(outputText, 'Successfully tagged') + const dockerFile = Uri.file(path.join(testRootFolder, 'Dockerfile')); + + try { + await runWithSetting('commands.build', `docker build --pull --rm -f "\${dockerfile}" -t \${tag} "\${context}" > ${testOutputFile} 2>&1`, async () => { + await testUserInput.runWithInputs(buildInputs, async () => { + const taskFinishedPromise = new Promise((resolve) => { + const disposable = tasks.onDidEndTask(() => { + disposable.dispose(); + resolve(); + }); + }); + + await commands.executeCommand('vscode-docker.images.build', dockerFile); + + // Wait for the task to finish + await taskFinishedPromise; + }); + }); + + const outputText = bufferToString(await fse.readFile(testOutputFile)); + + assertEx.assertContains(outputText, 'Successfully built'); + assertEx.assertContains(outputText, 'Successfully tagged'); + } finally { + if (await fse.pathExists(testOutputFile)) { + await fse.unlink(testOutputFile); + } + } } // Go