@@ -6,6 +6,8 @@ import * as path from 'path';
66import * as sinon from 'sinon' ;
77import { Uri } from 'vscode' ;
88import { PythonEnvironment } from '../../../api' ;
9+ import { EventNames } from '../../../common/telemetry/constants' ;
10+ import * as telemetrySender from '../../../common/telemetry/sender' ;
911import { createDeferred } from '../../../common/utils/deferred' ;
1012import { FastPathOptions , tryFastPathGet } from '../../../managers/common/fastPath' ;
1113
@@ -47,22 +49,121 @@ function createOpts(overrides?: Partial<FastPathOptions>): FastPathTestOptions {
4749}
4850
4951suite ( 'tryFastPathGet' , ( ) => {
52+ let sendTelemetryStub : sinon . SinonStub ;
53+
54+ setup ( ( ) => {
55+ sendTelemetryStub = sinon . stub ( telemetrySender , 'sendTelemetryEvent' ) ;
56+ } ) ;
57+
58+ teardown ( ( ) => {
59+ sinon . restore ( ) ;
60+ } ) ;
61+
5062 test ( 'returns resolved env when persisted path exists and init not started' , async ( ) => {
5163 const { opts } = createOpts ( ) ;
5264 const result = await tryFastPathGet ( opts ) ;
5365
5466 assert . ok ( result , 'Should return a result' ) ;
5567 assert . strictEqual ( result ! . env . envId . id , 'test-env' ) ;
68+ assert . ok ( sendTelemetryStub . notCalled , 'Should not emit global cache telemetry for workspace scope' ) ;
5669 } ) ;
5770
58- test ( 'returns undefined when scope is undefined' , async ( ) => {
71+ test ( 'returns undefined when scope is undefined and no getGlobalPersistedPath ' , async ( ) => {
5972 const { opts } = createOpts ( { scope : undefined } ) ;
6073 const result = await tryFastPathGet ( opts ) ;
6174
6275 assert . strictEqual ( result , undefined ) ;
6376 assert . ok ( ( opts . getPersistedPath as sinon . SinonStub ) . notCalled ) ;
6477 } ) ;
6578
79+ test ( 'returns resolved env for global scope when getGlobalPersistedPath returns a path' , async ( ) => {
80+ const globalPath = path . resolve ( 'usr' , 'bin' , 'python3' ) ;
81+ const resolve = sinon . stub ( ) . resolves ( createMockEnv ( globalPath ) ) ;
82+ const { opts } = createOpts ( {
83+ scope : undefined ,
84+ getGlobalPersistedPath : sinon . stub ( ) . resolves ( globalPath ) ,
85+ resolve,
86+ } ) ;
87+ const result = await tryFastPathGet ( opts ) ;
88+
89+ assert . ok ( result , 'Should return a result for global scope' ) ;
90+ assert . strictEqual ( result ! . env . envId . id , 'test-env' ) ;
91+ assert . ok ( resolve . calledOnceWith ( globalPath ) , 'Should resolve the global persisted path' ) ;
92+ assert . ok ( ( opts . getPersistedPath as sinon . SinonStub ) . notCalled , 'Should not call workspace getPersistedPath' ) ;
93+
94+ // Verify cache hit telemetry
95+ assert . ok ( sendTelemetryStub . calledOnce , 'Should send telemetry for global cache hit' ) ;
96+ const [ eventName , , props ] = sendTelemetryStub . firstCall . args ;
97+ assert . strictEqual ( eventName , EventNames . GLOBAL_ENV_CACHE ) ;
98+ assert . strictEqual ( props . result , 'hit' ) ;
99+ assert . strictEqual ( props . managerLabel , 'test' ) ;
100+ } ) ;
101+
102+ test ( 'returns undefined for global scope when getGlobalPersistedPath returns undefined' , async ( ) => {
103+ const { opts } = createOpts ( {
104+ scope : undefined ,
105+ getGlobalPersistedPath : sinon . stub ( ) . resolves ( undefined ) ,
106+ } ) ;
107+ const result = await tryFastPathGet ( opts ) ;
108+
109+ assert . strictEqual ( result , undefined ) ;
110+
111+ // Verify cache miss telemetry
112+ assert . ok ( sendTelemetryStub . calledOnce , 'Should send telemetry for global cache miss' ) ;
113+ const [ eventName , , props ] = sendTelemetryStub . firstCall . args ;
114+ assert . strictEqual ( eventName , EventNames . GLOBAL_ENV_CACHE ) ;
115+ assert . strictEqual ( props . result , 'miss' ) ;
116+ } ) ;
117+
118+ test ( 'reports stale when global cached path resolves to undefined' , async ( ) => {
119+ const globalPath = path . resolve ( 'usr' , 'bin' , 'python3' ) ;
120+ const { opts } = createOpts ( {
121+ scope : undefined ,
122+ getGlobalPersistedPath : sinon . stub ( ) . resolves ( globalPath ) ,
123+ resolve : sinon . stub ( ) . resolves ( undefined ) ,
124+ } ) ;
125+ const result = await tryFastPathGet ( opts ) ;
126+
127+ assert . strictEqual ( result , undefined , 'Should fall through when cached env resolves to undefined' ) ;
128+ assert . ok ( sendTelemetryStub . calledOnce , 'Should send telemetry for stale cache' ) ;
129+ const [ eventName , , props ] = sendTelemetryStub . firstCall . args ;
130+ assert . strictEqual ( eventName , EventNames . GLOBAL_ENV_CACHE ) ;
131+ assert . strictEqual ( props . result , 'stale' ) ;
132+ } ) ;
133+
134+ test ( 'returns undefined for global scope when cached path resolve fails' , async ( ) => {
135+ const globalPath = path . resolve ( 'usr' , 'bin' , 'python3' ) ;
136+ const { opts } = createOpts ( {
137+ scope : undefined ,
138+ getGlobalPersistedPath : sinon . stub ( ) . resolves ( globalPath ) ,
139+ resolve : sinon . stub ( ) . rejects ( new Error ( 'python was uninstalled' ) ) ,
140+ } ) ;
141+ const result = await tryFastPathGet ( opts ) ;
142+
143+ assert . strictEqual ( result , undefined , 'Should fall through when cached global env is stale' ) ;
144+
145+ // Verify cache stale telemetry
146+ assert . ok ( sendTelemetryStub . calledOnce , 'Should send telemetry for stale global cache' ) ;
147+ const [ eventName , , props ] = sendTelemetryStub . firstCall . args ;
148+ assert . strictEqual ( eventName , EventNames . GLOBAL_ENV_CACHE ) ;
149+ assert . strictEqual ( props . result , 'stale' ) ;
150+ } ) ;
151+
152+ test ( 'global scope fast path starts background init when initialized is undefined' , async ( ) => {
153+ const globalPath = path . resolve ( 'usr' , 'bin' , 'python3' ) ;
154+ const startBackgroundInit = sinon . stub ( ) . resolves ( ) ;
155+ const { opts, setInitialized } = createOpts ( {
156+ scope : undefined ,
157+ getGlobalPersistedPath : sinon . stub ( ) . resolves ( globalPath ) ,
158+ startBackgroundInit,
159+ } ) ;
160+ const result = await tryFastPathGet ( opts ) ;
161+
162+ assert . ok ( result , 'Should return fast-path result' ) ;
163+ assert . ok ( startBackgroundInit . calledOnce , 'Should start background init for global scope' ) ;
164+ assert . ok ( setInitialized . calledOnce , 'Should set initialized for global scope' ) ;
165+ } ) ;
166+
66167 test ( 'returns undefined when init is already completed' , async ( ) => {
67168 const deferred = createDeferred < void > ( ) ;
68169 deferred . resolve ( ) ;
0 commit comments