Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send all stderr and stdout to the output channel when a DVC process fails #3857

Merged
merged 3 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions extension/src/cli/dvc/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ export class DvcRunner extends Disposable implements ICli {
{
...baseEvent,
duration: stopWatch.getElapsedTime(),
exitCode,
stderr
errorOutput: stderr,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[F] The runner and viewer show processes in the foreground so there is no change for these

exitCode
},
this.processCompleted
)
Expand Down
30 changes: 23 additions & 7 deletions extension/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Event, EventEmitter } from 'vscode'
import { getCommandString } from './command'
import { CliError, MaybeConsoleError } from './error'
import { notifyCompleted, notifyStarted } from './util'
import { notifyCompleted, notifyStarted, transformChunkToString } from './util'
import { createProcess, Process, ProcessOptions } from '../process/execution'
import { StopWatch } from '../util/time'
import { Disposable } from '../class/dispose'
Expand All @@ -13,7 +13,7 @@ export type CliEvent = {
}

export type CliResult = CliEvent & {
stderr?: string
errorOutput?: string
duration: number
exitCode: number | null
}
Expand Down Expand Up @@ -72,13 +72,19 @@ export class Cli extends Disposable implements ICli {

protected async executeProcess(options: ProcessOptions): Promise<string> {
const { baseEvent, stopWatch } = this.getProcessDetails(options)
let all = ''
try {
const process = this.dispose.track(this.createProcess(baseEvent, options))

void process.on('close', () => {
void this.dispose.untrack(process)
})

process.all?.on(
'data',
chunk => (all += transformChunkToString(chunk as Buffer))
)

const { stdout, exitCode } = await process

notifyCompleted(
Expand All @@ -96,18 +102,26 @@ export class Cli extends Disposable implements ICli {
error as MaybeConsoleError,
options,
baseEvent,
stopWatch.getElapsedTime()
stopWatch.getElapsedTime(),
all
)
}
}

protected async createBackgroundProcess(options: ProcessOptions) {
const { baseEvent, stopWatch } = this.getProcessDetails(options)
let all = ''
try {
const backgroundProcess = this.createProcess(baseEvent, {
detached: true,
...options
})

backgroundProcess.all?.on(
'data',
chunk => (all += transformChunkToString(chunk as Buffer))
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be moved to a private function, or even a util (just because it's being duplicated from line 83)? That would also make sure this line is covered since line 83 is.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did have this in a separate helper function but the string was not captured as expected and passed to the catch block.


return await this.getOutputAndDisconnect(
baseEvent,
backgroundProcess,
Expand All @@ -118,7 +132,8 @@ export class Cli extends Disposable implements ICli {
error as MaybeConsoleError,
options,
baseEvent,
stopWatch.getElapsedTime()
stopWatch.getElapsedTime(),
all
)
}
}
Expand Down Expand Up @@ -170,7 +185,8 @@ export class Cli extends Disposable implements ICli {
error: MaybeConsoleError,
options: ProcessOptions,
baseEvent: CliEvent,
duration: number
duration: number,
all: string
) {
const cliError = new CliError({
baseError: error,
Expand All @@ -180,8 +196,8 @@ export class Cli extends Disposable implements ICli {
{
...baseEvent,
duration,
exitCode: cliError.exitCode,
stderr: cliError.stderr
errorOutput: all || cliError.stderr,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[F] If we have captured a valid all string (length > 0) then we will use that, otherwise use the stderr from the generated error. I feel like this way there will always be something provided to the output channel

exitCode: cliError.exitCode
},
this.processCompleted
)
Expand Down
19 changes: 10 additions & 9 deletions extension/src/cli/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,32 @@ export const notifyStarted = (
processStarted: EventEmitter<CliEvent>
): void => processStarted.fire(baseEvent)

export const transformChunkToString = (chunk: Buffer): string =>
chunk
.toString()
.split(/(\r?\n)/g)
.join('\r')

export const notifyOutput = (
process: Process,
processOutput: EventEmitter<string>
): void => {
process.all?.on('data', chunk =>
processOutput.fire(
(chunk as Buffer)
.toString()
.split(/(\r?\n)/g)
.join('\r')
)
processOutput.fire(transformChunkToString(chunk as Buffer))
)
}

export const notifyCompleted = (
{ command, pid, cwd, duration, exitCode, stderr }: CliResult,
{ command, pid, cwd, duration, exitCode, errorOutput: stderr }: CliResult,
processCompleted: EventEmitter<CliResult>
): void =>
processCompleted.fire({
command,
cwd,
duration,
errorOutput: stderr?.replace(/\n+/g, '\n'),
exitCode,
pid,
stderr: stderr?.replace(/\n+/g, '\n')
pid
})

export const captureStdErr = (process: Process): string => {
Expand Down
4 changes: 2 additions & 2 deletions extension/src/cli/viewable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export class ViewableCliProcess extends DeferredDisposable {
{
...baseEvent,
duration: stopWatch.getElapsedTime(),
exitCode,
stderr
errorOutput: stderr,
exitCode
},
processCompleted
)
Expand Down
6 changes: 3 additions & 3 deletions extension/src/test/suite/vscode/outputChannel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ suite('Output Channel Test Suite', () => {
command: 'some command',
cwd,
duration: 20,
errorOutput:
'THIS IS AN IMPOSSIBLE ERROR. THIS ERROR CANNOT OCCUR. IF THIS ERROR OCCURS, SEE YOUR IBM REPRESENTATIVE.',
exitCode: -9,
pid: 12345,
stderr:
'THIS IS AN IMPOSSIBLE ERROR. THIS ERROR CANNOT OCCUR. IF THIS ERROR OCCURS, SEE YOUR IBM REPRESENTATIVE.'
pid: 12345
})

expect(mockOutputChannel).to.be.called
Expand Down
14 changes: 8 additions & 6 deletions extension/src/vscode/outputChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ export class OutputChannel extends Disposable {
private onDidCompleteProcess(cli: ICli) {
this.dispose.track(
cli.onDidCompleteProcess(
({ command, duration, exitCode, pid, stderr }) => {
({ command, duration, exitCode, pid, errorOutput }) => {
const processStatus =
exitCode && stderr ? ProcessStatus.FAILED : ProcessStatus.COMPLETED
exitCode && errorOutput
? ProcessStatus.FAILED
: ProcessStatus.COMPLETED

const baseOutput = this.getBaseOutput(pid, command, processStatus)
const completionOutput = this.getCompletionOutput(
exitCode,
duration,
stderr
errorOutput
)

return this.outputChannel.append(`${baseOutput}${completionOutput}\n`)
Expand All @@ -75,7 +77,7 @@ export class OutputChannel extends Disposable {
private getCompletionOutput(
exitCode: number | null,
duration: number,
stderr?: string
errorOutput?: string
) {
let completionOutput = ''
if (exitCode) {
Expand All @@ -84,8 +86,8 @@ export class OutputChannel extends Disposable {

completionOutput += ` (${duration}ms)`

if (exitCode && stderr) {
completionOutput += `\n${stderr}`
if (exitCode && errorOutput) {
completionOutput += `\n${errorOutput}`
}

return completionOutput
Expand Down