Skip to content

Commit 475574e

Browse files
authored
Recheck for global CLI becoming available (#2928)
* remove recheck from call to setup * test and refactor
1 parent 7f23202 commit 475574e

File tree

7 files changed

+157
-38
lines changed

7 files changed

+157
-38
lines changed

extension/src/cli/dvc/discovery.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
isPythonExtensionInstalled,
1818
selectPythonInterpreter
1919
} from '../../extensions/python'
20+
import { getFirstWorkspaceFolder } from '../../vscode/workspaceFolders'
21+
import { delay } from '../../util/time'
2022

2123
const getToastOptions = (isPythonExtensionInstalled: boolean): Response[] => {
2224
return isPythonExtensionInstalled
@@ -116,7 +118,9 @@ type CanRunCli = {
116118
isCompatible: boolean | undefined
117119
}
118120

119-
const isCliCompatible = (cliCompatible: CliCompatible): boolean | undefined => {
121+
export const isCliCompatible = (
122+
cliCompatible: CliCompatible
123+
): boolean | undefined => {
120124
if (cliCompatible === CliCompatible.NO_NOT_FOUND) {
121125
return
122126
}
@@ -159,17 +163,15 @@ const processVersionDetails = (
159163

160164
const tryGlobalFallbackVersion = async (
161165
extension: IExtension,
162-
cwd: string,
163-
recheck: boolean
166+
cwd: string
164167
): Promise<CanRunCli> => {
165168
const tryGlobal = await getVersionDetails(extension, cwd, true)
166169
const { cliCompatible, isAvailable, isCompatible, version } = tryGlobal
167170

168-
if (!recheck && extension.hasRoots() && !isCompatible) {
171+
if (extension.hasRoots() && !isCompatible) {
169172
warnUserCLIInaccessibleAnywhere(extension, version)
170173
}
171174
if (
172-
!recheck &&
173175
extension.hasRoots() &&
174176
cliCompatible === CliCompatible.YES_MINOR_VERSION_AHEAD_OF_TESTED
175177
) {
@@ -185,8 +187,7 @@ const tryGlobalFallbackVersion = async (
185187

186188
const extensionCanAutoRunCli = async (
187189
extension: IExtension,
188-
cwd: string,
189-
recheck: boolean
190+
cwd: string
190191
): Promise<CanRunCli> => {
191192
const {
192193
cliCompatible: pythonCliCompatible,
@@ -196,7 +197,7 @@ const extensionCanAutoRunCli = async (
196197
} = await getVersionDetails(extension, cwd)
197198

198199
if (pythonCliCompatible === CliCompatible.NO_NOT_FOUND) {
199-
return tryGlobalFallbackVersion(extension, cwd, recheck)
200+
return tryGlobalFallbackVersion(extension, cwd)
200201
}
201202
return processVersionDetails(
202203
extension,
@@ -209,11 +210,10 @@ const extensionCanAutoRunCli = async (
209210

210211
export const extensionCanRunCli = async (
211212
extension: IExtension,
212-
cwd: string,
213-
recheck: boolean
213+
cwd: string
214214
): Promise<CanRunCli> => {
215215
if (await extension.isPythonExtensionUsed()) {
216-
return extensionCanAutoRunCli(extension, cwd, recheck)
216+
return extensionCanAutoRunCli(extension, cwd)
217217
}
218218

219219
const { cliCompatible, isAvailable, isCompatible, version } =
@@ -227,3 +227,27 @@ export const extensionCanRunCli = async (
227227
isCompatible
228228
)
229229
}
230+
231+
export const recheckGlobal = async (
232+
extension: IExtension,
233+
setup: () => Promise<void[] | undefined>,
234+
recheckInterval: number
235+
): Promise<void> => {
236+
await delay(recheckInterval)
237+
const roots = extension.getRoots()
238+
const cwd = roots.length > 0 ? roots[0] : getFirstWorkspaceFolder()
239+
240+
if (!cwd || extension.getAvailable()) {
241+
return
242+
}
243+
244+
const version = await extension.getCliVersion(cwd, true)
245+
const cliCompatible = isVersionCompatible(version)
246+
const isCompatible = isCliCompatible(cliCompatible)
247+
248+
if (!isCompatible) {
249+
return recheckGlobal(extension, setup, recheckInterval)
250+
}
251+
252+
setup()
253+
}

extension/src/extension.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { IExtension } from './interfaces'
2121
import { registerRepositoryCommands } from './repository/commands/register'
2222
import { ResourceLocator } from './resourceLocator'
2323
import { definedAndNonEmpty } from './util/array'
24-
import { setup, setupWorkspace } from './setup'
24+
import { setup, setupWithGlobalRecheck, setupWorkspace } from './setup'
2525
import { Status } from './status'
2626
import { reRegisterVsCodeCommands } from './vscode/commands'
2727
import { InternalCommands } from './commands/internal'
@@ -204,14 +204,14 @@ export class Extension extends Disposable implements IExtension {
204204
new RepositoriesTree(this.internalCommands, this.repositories)
205205
)
206206

207-
setup(this)
208-
.then(async () =>
207+
setupWithGlobalRecheck(this)
208+
.then(async () => {
209209
sendTelemetryEvent(
210210
EventName.EXTENSION_LOAD,
211211
await this.getEventProperties(),
212212
{ duration: stopWatch.getElapsedTime() }
213213
)
214-
)
214+
})
215215
.catch(async error =>
216216
sendTelemetryEventAndThrow(
217217
EventName.EXTENSION_LOAD,

extension/src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface IExtension {
1313
resetMembers: () => void
1414

1515
setAvailable: (available: boolean) => void
16+
getAvailable: () => boolean
1617
setCliCompatible: (compatible: boolean | undefined) => void
1718
setRoots: () => Promise<void>
1819
unsetPythonBinPath: () => void

extension/src/setup.test.ts

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { resolve } from 'path'
22
import { extensions, Extension, commands } from 'vscode'
3-
import { setup, setupWorkspace } from './setup'
3+
import { setup, setupWithGlobalRecheck, setupWorkspace } from './setup'
44
import { flushPromises } from './test/util/jest'
55
import {
66
getConfigValue,
@@ -24,6 +24,7 @@ import {
2424
MIN_CLI_VERSION
2525
} from './cli/dvc/constants'
2626
import { extractSemver, ParsedSemver } from './cli/dvc/version'
27+
import { delay } from './util/time'
2728

2829
jest.mock('vscode')
2930
jest.mock('./vscode/config')
@@ -32,7 +33,6 @@ jest.mock('./vscode/quickPick')
3233
jest.mock('./vscode/toast')
3334
jest.mock('./vscode/workspaceFolders')
3435
jest.mock('./processExecution')
35-
jest.mock('./setupUtil')
3636

3737
const mockedExtensions = jest.mocked(extensions)
3838
const mockedCommands = jest.mocked(commands)
@@ -63,6 +63,7 @@ const mockedVscodePython = {
6363
}
6464

6565
const mockedCwd = __dirname
66+
const mockedGetAvailable = jest.fn()
6667
const mockedGetCliVersion = jest.fn()
6768
const mockedGetFirstWorkspaceFolder = jest.mocked(getFirstWorkspaceFolder)
6869
const mockedHasRoots = jest.fn()
@@ -259,6 +260,7 @@ describe('setupWorkspace', () => {
259260

260261
describe('setup', () => {
261262
const extension = {
263+
getAvailable: mockedGetAvailable,
262264
getCliVersion: mockedGetCliVersion,
263265
getRoots: mockedGetRoots,
264266
hasRoots: mockedHasRoots,
@@ -618,3 +620,102 @@ describe('setup', () => {
618620
expect(mockedInitialize).not.toHaveBeenCalled()
619621
})
620622
})
623+
624+
describe('setupWithGlobalRecheck', () => {
625+
const extension = {
626+
getAvailable: mockedGetAvailable,
627+
getCliVersion: mockedGetCliVersion,
628+
getRoots: mockedGetRoots,
629+
hasRoots: mockedHasRoots,
630+
initialize: mockedInitialize,
631+
isPythonExtensionUsed: mockedIsPythonExtensionUsed,
632+
resetMembers: mockedResetMembers,
633+
setAvailable: mockedSetAvailable,
634+
setCliCompatible: mockedSetCliCompatible,
635+
setRoots: mockedSetRoots,
636+
setupWorkspace: mockedSetupWorkspace,
637+
unsetPythonBinPath: mockedUnsetPythonBinPath
638+
}
639+
640+
it('should periodically check to see if the CLI is available globally if the extension fails to initialize', async () => {
641+
mockedGetFirstWorkspaceFolder.mockReturnValue(mockedCwd)
642+
643+
const mockedRecheckInterval = 0
644+
645+
mockedGetAvailable
646+
.mockReturnValueOnce(false)
647+
.mockReturnValueOnce(false)
648+
.mockReturnValueOnce(false)
649+
650+
mockedGetCliVersion
651+
.mockResolvedValueOnce(undefined)
652+
.mockResolvedValueOnce(undefined)
653+
.mockResolvedValueOnce(LATEST_TESTED_CLI_VERSION)
654+
.mockResolvedValueOnce(LATEST_TESTED_CLI_VERSION)
655+
656+
await setupWithGlobalRecheck(extension, mockedRecheckInterval)
657+
658+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
659+
expect(mockedGetAvailable).toHaveBeenCalledTimes(1)
660+
661+
await delay(mockedRecheckInterval)
662+
663+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(2)
664+
expect(mockedGetAvailable).toHaveBeenCalledTimes(2)
665+
666+
await delay(mockedRecheckInterval)
667+
668+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(4)
669+
expect(mockedGetAvailable).toHaveBeenCalledTimes(3)
670+
671+
await delay(mockedRecheckInterval)
672+
673+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(4)
674+
expect(mockedGetAvailable).toHaveBeenCalledTimes(3)
675+
})
676+
677+
it('should not periodically check to see if the CLI is available globally if the extension initializes', async () => {
678+
mockedGetFirstWorkspaceFolder.mockReturnValue(mockedCwd)
679+
680+
mockedGetAvailable.mockReturnValueOnce(true)
681+
682+
mockedGetCliVersion.mockResolvedValueOnce(LATEST_TESTED_CLI_VERSION)
683+
684+
await setupWithGlobalRecheck(extension, 0)
685+
686+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
687+
expect(mockedGetAvailable).toHaveBeenCalledTimes(1)
688+
689+
await delay(0)
690+
691+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
692+
expect(mockedGetAvailable).toHaveBeenCalledTimes(1)
693+
694+
await delay(0)
695+
696+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
697+
expect(mockedGetAvailable).toHaveBeenCalledTimes(1)
698+
})
699+
700+
it('should stop checking if the extension is available globally if it initializes', async () => {
701+
mockedGetFirstWorkspaceFolder.mockReturnValue(mockedCwd)
702+
703+
mockedGetAvailable.mockReturnValueOnce(false).mockReturnValueOnce(true)
704+
mockedGetCliVersion.mockResolvedValueOnce(undefined)
705+
706+
await setupWithGlobalRecheck(extension, 0)
707+
708+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
709+
expect(mockedGetAvailable).toHaveBeenCalledTimes(1)
710+
711+
await delay(0)
712+
713+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
714+
expect(mockedGetAvailable).toHaveBeenCalledTimes(2)
715+
716+
await delay(0)
717+
718+
expect(mockedGetCliVersion).toHaveBeenCalledTimes(1)
719+
expect(mockedGetAvailable).toHaveBeenCalledTimes(2)
720+
})
721+
})

extension/src/setup.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ import { pickFile } from './vscode/resourcePicker'
99
import { getFirstWorkspaceFolder } from './vscode/workspaceFolders'
1010
import { getSelectTitle, Title } from './vscode/title'
1111
import { isPythonExtensionInstalled } from './extensions/python'
12-
import { extensionCanRunCli } from './cli/dvc/discovery'
13-
import { willRecheck } from './setupUtil'
12+
import { extensionCanRunCli, recheckGlobal } from './cli/dvc/discovery'
1413

1514
const setConfigPath = async (
1615
option: ConfigKey,
@@ -156,13 +155,11 @@ export const setupWorkspace = async (): Promise<boolean> => {
156155

157156
export const checkAvailable = async (
158157
extension: IExtension,
159-
dvcRootOrFirstFolder: string,
160-
recheck = false
158+
dvcRootOrFirstFolder: string
161159
) => {
162160
const { isAvailable, isCompatible } = await extensionCanRunCli(
163161
extension,
164-
dvcRootOrFirstFolder,
165-
recheck
162+
dvcRootOrFirstFolder
166163
)
167164

168165
extension.setCliCompatible(isCompatible)
@@ -173,11 +170,6 @@ export const checkAvailable = async (
173170
}
174171

175172
extension.resetMembers()
176-
177-
if (!isAvailable) {
178-
extension.setAvailable(false)
179-
willRecheck(extension, dvcRootOrFirstFolder)
180-
}
181173
}
182174

183175
export const setup = async (extension: IExtension) => {
@@ -193,3 +185,14 @@ export const setup = async (extension: IExtension) => {
193185

194186
return checkAvailable(extension, dvcRootOrFirstFolder)
195187
}
188+
189+
export const setupWithGlobalRecheck = async (
190+
extension: IExtension,
191+
recheckInterval = 5000
192+
): Promise<void> => {
193+
await setup(extension)
194+
195+
if (!extension.getAvailable()) {
196+
recheckGlobal(extension, () => setup(extension), recheckInterval)
197+
}
198+
}

extension/src/setupUtil.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

extension/src/test/suite/extension.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ suite('Extension Test Suite', () => {
5555
])
5656
})
5757

58-
// eslint-disable-next-line sonarjs/cognitive-complexity
5958
describe('dvc.setupWorkspace', () => {
6059
it('should set dvc.dvcPath to the default when dvc is installed in a virtual environment', async () => {
6160
stub(Python, 'isPythonExtensionInstalled').returns(true)

0 commit comments

Comments
 (0)