diff --git a/extension/src/cli/constants.ts b/extension/src/cli/constants.ts index 36d13c97a6..5ff51a6897 100644 --- a/extension/src/cli/constants.ts +++ b/extension/src/cli/constants.ts @@ -2,6 +2,8 @@ export const MIN_CLI_VERSION = '2.11.0' export const LATEST_TESTED_CLI_VERSION = '2.13.0' export const MAX_CLI_VERSION = '3' +export const UNEXPECTED_ERROR_CODE = 255 + export enum Command { ADD = 'add', CHECKOUT = 'checkout', diff --git a/extension/src/cli/reader.test.ts b/extension/src/cli/reader.test.ts index e5524aa124..683e21fa42 100644 --- a/extension/src/cli/reader.test.ts +++ b/extension/src/cli/reader.test.ts @@ -2,7 +2,9 @@ import { join } from 'path' import { EventEmitter } from 'vscode' import { Disposable, Disposer } from '@hediet/std/disposable' import { CliResult, CliStarted } from '.' -import { CliReader } from './reader' +import { UNEXPECTED_ERROR_CODE } from './constants' +import { MaybeConsoleError } from './error' +import { CliReader, ExperimentsOutput } from './reader' import { createProcess } from '../processExecution' import { getFailingMockedProcess, getMockedProcess } from '../test/util/jest' import { getProcessEnv } from '../env' @@ -76,6 +78,39 @@ describe('CliReader', () => { executable: 'dvc' }) }) + + it('should return the default output if the cli returns an unexpected error (255 exit code)', async () => { + const cwd = __dirname + const error = new Error('unexpected error - something something') + ;(error as MaybeConsoleError).exitCode = UNEXPECTED_ERROR_CODE + mockedCreateProcess.mockImplementationOnce(() => { + throw error + }) + + const cliOutput = await cliReader.expShow(cwd) + expect(cliOutput).toStrictEqual({ workspace: { baseline: {} } }) + }) + + it('should retry the cli given any other type of error', async () => { + const cwd = __dirname + const mockOutput: ExperimentsOutput = { + workspace: { + baseline: { + data: { params: { 'params.yaml': { data: { epochs: 100000000 } } } } + } + } + } + mockedCreateProcess.mockImplementationOnce(() => { + throw new Error('error that should be retried') + }) + mockedCreateProcess.mockReturnValueOnce( + getMockedProcess(JSON.stringify(mockOutput)) + ) + + const cliOutput = await cliReader.expShow(cwd) + expect(cliOutput).toStrictEqual(mockOutput) + expect(mockedCreateProcess).toBeCalledTimes(2) + }) }) describe('diff', () => { diff --git a/extension/src/cli/reader.ts b/extension/src/cli/reader.ts index 7c2f6ec166..889714a648 100644 --- a/extension/src/cli/reader.ts +++ b/extension/src/cli/reader.ts @@ -108,6 +108,10 @@ export interface ExperimentsOutput { } } +const defaultExperimentsOutput: ExperimentsOutput = { + workspace: { baseline: {} } +} + export interface PlotsOutput { [path: string]: Plot[] } @@ -132,11 +136,14 @@ export class CliReader extends Cli { cwd: string, ...flags: ExperimentFlag[] ): Promise { - return this.readProcessJson( + return this.readProcess( cwd, + JSON.parse, + JSON.stringify(defaultExperimentsOutput), Command.EXPERIMENT, SubCommand.SHOW, - ...flags + ...flags, + Flag.SHOW_JSON ) } @@ -158,7 +165,7 @@ export class CliReader extends Cli { return this.readProcessJson( cwd, Command.PLOTS, - 'diff', + Command.DIFF, ...revisions, Flag.OUTPUT_PATH, TEMP_PLOTS_DIR, diff --git a/extension/src/cli/retry.test.ts b/extension/src/cli/retry.test.ts index 0378833ae3..d034fef76d 100644 --- a/extension/src/cli/retry.test.ts +++ b/extension/src/cli/retry.test.ts @@ -17,7 +17,7 @@ describe('retry', () => { const promiseRefresher = jest.fn().mockImplementation(() => promise()) - const output = await retry(promiseRefresher, 'Definitely did not') + const output = await retry(promiseRefresher, 'Definitely did not') expect(output).toStrictEqual(returnValue) @@ -45,7 +45,7 @@ describe('retry', () => { .fn() .mockImplementation(() => unreliablePromise()) - await retry(promiseRefresher, 'Data update') + await retry(promiseRefresher, 'Data update') expect(promiseRefresher).toBeCalledTimes(4) expect(mockedDelay).toBeCalledTimes(3) diff --git a/extension/src/cli/retry.ts b/extension/src/cli/retry.ts index 3ce1ca3b29..6ed2e34d00 100644 --- a/extension/src/cli/retry.ts +++ b/extension/src/cli/retry.ts @@ -1,17 +1,27 @@ +import { UNEXPECTED_ERROR_CODE } from './constants' +import { MaybeConsoleError } from './error' import { delay } from '../util/time' import { Logger } from '../common/logger' -export const retry = async ( - getNewPromise: () => Promise, +const isUnexpectedError = (error: unknown): boolean => { + return (error as MaybeConsoleError)?.exitCode === UNEXPECTED_ERROR_CODE +} + +export const retry = async ( + getNewPromise: () => Promise, args: string, waitBeforeRetry = 500 -): Promise => { +): Promise => { try { return await getNewPromise() } catch (error: unknown) { const errorMessage = (error as Error).message Logger.error(`${args} failed with ${errorMessage} retrying...`) + if (isUnexpectedError(error)) { + return '' + } + await delay(waitBeforeRetry) return retry(getNewPromise, args, waitBeforeRetry * 2) } diff --git a/extension/src/experiments/data/collect.ts b/extension/src/experiments/data/collect.ts index 227c8aa4ad..30add5506a 100644 --- a/extension/src/experiments/data/collect.ts +++ b/extension/src/experiments/data/collect.ts @@ -3,8 +3,8 @@ import { ExperimentsOutput } from '../../cli/reader' export const collectFiles = (data: ExperimentsOutput): string[] => { const files = new Set( Object.keys({ - ...data?.workspace.baseline?.data?.params, - ...data?.workspace.baseline?.data?.metrics + ...data?.workspace?.baseline?.data?.params, + ...data?.workspace?.baseline?.data?.metrics }).filter(Boolean) ) diff --git a/extension/src/experiments/index.ts b/extension/src/experiments/index.ts index 172715e3c3..18236a9326 100644 --- a/extension/src/experiments/index.ts +++ b/extension/src/experiments/index.ts @@ -277,6 +277,10 @@ export class Experiments extends BaseRepository { } public getExperimentCount() { + if (!this.columns.hasColumns()) { + return 0 + } + return this.experiments.getExperimentCount() } diff --git a/extension/src/experiments/model/tree.ts b/extension/src/experiments/model/tree.ts index b55eb6eac2..bfc5da4f95 100644 --- a/extension/src/experiments/model/tree.ts +++ b/extension/src/experiments/model/tree.ts @@ -268,7 +268,14 @@ export class ExperimentsTree private getDescription() { const dvcRoots = this.experiments.getDvcRoots() - if (!definedAndNonEmpty(dvcRoots)) { + + const total = sum( + dvcRoots.map(dvcRoot => + this.experiments.getRepository(dvcRoot).getExperimentCount() + ) + ) + + if (!total) { return } @@ -279,12 +286,6 @@ export class ExperimentsTree ) ) - const total = sum( - dvcRoots.map(dvcRoot => - this.experiments.getRepository(dvcRoot).getExperimentCount() - ) - ) - return `${selected} of ${total} (max ${MAX_SELECTED_EXPERIMENTS})` }