@@ -5803,35 +5803,41 @@ suite('InlineScriptEnvManager', () => {
58035803 assert . strictEqual ( await fs . pathExists ( stale . sysPrefix ) , true ) ;
58045804 } ) ;
58055805
5806- test ( 'invalidates associations and discovered environments for deleted entries' , async ( ) => {
5807- const uri = scriptUri ( 'associated.py' ) ;
5806+ test ( 'removes discovered environments for evicted orphaned entries' , async ( ) => {
58085807 const stale = await createOwnedEnvironment ( 'aaaaaaaaaaaaaaaa' ) ;
58095808 await setLastUsedAt ( stale , new Date ( NOW . getTime ( ) - TTL_MS - 1 ) ) ;
5810- await manager . set ( uri , stale ) ;
58115809 ( manager as unknown as { collection : PythonEnvironment [ ] } ) . collection = [ stale ] ;
5812- const selectionListener = sinon . spy ( ) ;
58135810 const collectionListener = sinon . spy ( ) ;
5814- manager . onDidChangeEnvironment ( selectionListener ) ;
58155811 manager . onDidChangeEnvironments ( collectionListener ) ;
58165812
58175813 assert . ok ( await manager . create ( scriptUri ( 'trigger.py' ) ) ) ;
58185814
5819- assert . strictEqual ( await manager . get ( uri ) , undefined ) ;
5820- assert . strictEqual ( persistedAssociations , undefined ) ;
5815+ assert . strictEqual ( await fs . pathExists ( stale . sysPrefix ) , false ) ;
58215816 assert . deepStrictEqual ( await manager . getEnvironments ( 'all' ) , [ ] ) ;
5822- sinon . assert . calledOnce ( selectionListener ) ;
5823- assert . strictEqual (
5824- normalizePath ( selectionListener . firstCall . args [ 0 ] . uri . fsPath ) ,
5825- normalizePath ( uri . fsPath ) ,
5826- ) ;
5827- assert . strictEqual ( selectionListener . firstCall . args [ 0 ] . old , stale ) ;
5828- assert . strictEqual ( selectionListener . firstCall . args [ 0 ] . new , undefined ) ;
58295817 sinon . assert . calledOnceWithExactly ( collectionListener , [
58305818 { kind : EnvironmentChangeKind . remove , environment : stale } ,
58315819 ] ) ;
58325820 } ) ;
58335821
5834- test ( 'invalidates an association when another host removes the stale entry first' , async ( ) => {
5822+ test ( 'preserves a stale entry and its association while a script still references it' , async ( ) => {
5823+ const uri = scriptUri ( 'associated.py' ) ;
5824+ const stale = await createOwnedEnvironment ( 'aaaaaaaaaaaaaaaa' ) ;
5825+ await setLastUsedAt ( stale , new Date ( NOW . getTime ( ) - TTL_MS - 1 ) ) ;
5826+ await manager . set ( uri , stale ) ;
5827+ ( manager as unknown as { collection : PythonEnvironment [ ] } ) . collection = [ stale ] ;
5828+ const selectionListener = sinon . spy ( ) ;
5829+ manager . onDidChangeEnvironment ( selectionListener ) ;
5830+
5831+ assert . ok ( await manager . create ( scriptUri ( 'trigger.py' ) ) ) ;
5832+
5833+ assert . strictEqual ( await fs . pathExists ( stale . sysPrefix ) , true ) ;
5834+ assert . strictEqual ( await manager . get ( uri ) , stale ) ;
5835+ assert . notStrictEqual ( persistedAssociations , undefined ) ;
5836+ assert . ok ( ( await manager . getEnvironments ( 'all' ) ) . some ( ( env ) => env === stale ) ) ;
5837+ sinon . assert . notCalled ( selectionListener ) ;
5838+ } ) ;
5839+
5840+ test ( 'does not attempt to remove a stale entry that a script still references' , async ( ) => {
58355841 const uri = scriptUri ( 'associated.py' ) ;
58365842 const stale = await createOwnedEnvironment ( 'aaaaaaaaaaaaaaaa' ) ;
58375843 await setLastUsedAt ( stale , new Date ( NOW . getTime ( ) - TTL_MS - 1 ) ) ;
@@ -5847,15 +5853,14 @@ suite('InlineScriptEnvManager', () => {
58475853 } ,
58485854 ) : Promise < string | undefined > ;
58495855 } ;
5850- sinon . stub ( internalManager , 'removeCacheEntryForClear' ) . callsFake ( async ( ) => {
5851- await fs . remove ( stale . sysPrefix ) ;
5852- return undefined ;
5853- } ) ;
5856+ const removeSpy = sinon . spy ( internalManager , 'removeCacheEntryForClear' ) ;
58545857
58555858 assert . ok ( await manager . create ( scriptUri ( 'trigger.py' ) ) ) ;
58565859
5857- assert . strictEqual ( persistedAssociations , undefined ) ;
5858- assert . strictEqual ( await manager . get ( uri ) , undefined ) ;
5860+ sinon . assert . notCalled ( removeSpy ) ;
5861+ assert . strictEqual ( await fs . pathExists ( stale . sysPrefix ) , true ) ;
5862+ assert . strictEqual ( await manager . get ( uri ) , stale ) ;
5863+ assert . notStrictEqual ( persistedAssociations , undefined ) ;
58595864 } ) ;
58605865
58615866 test ( 'does not let an in-flight refresh re-add an evicted environment' , async ( ) => {
@@ -5913,14 +5918,19 @@ suite('InlineScriptEnvManager', () => {
59135918 } ) ;
59145919
59155920 test ( 'does not fail creation when association cleanup cannot be persisted' , async ( ) => {
5916- const uri = scriptUri ( 'associated.py' ) ;
5917- const stale = await createOwnedEnvironment ( 'aaaaaaaaaaaaaaaa' ) ;
5918- await setLastUsedAt ( stale , new Date ( NOW . getTime ( ) - TTL_MS - 1 ) ) ;
5919- await manager . set ( uri , stale ) ;
5921+ const orphan = await createOwnedEnvironment ( 'aaaaaaaaaaaaaaaa' ) ;
5922+ await setLastUsedAt ( orphan , new Date ( NOW . getTime ( ) - TTL_MS - 1 ) ) ;
5923+ // A separate association whose environment was deleted out from under us. Evicting the
5924+ // orphaned entry above drives the association cleanup pass, and persisting that cleanup is
5925+ // what fails here.
5926+ const missingUri = scriptUri ( 'missing.py' ) ;
5927+ const missing = await createOwnedEnvironment ( 'bbbbbbbbbbbbbbbb' ) ;
5928+ await manager . set ( missingUri , missing ) ;
5929+ await fs . remove ( missing . sysPrefix ) ;
59205930 workspaceState . update . onSecondCall ( ) . rejects ( new Error ( 'Memento unavailable' ) ) ;
59215931
59225932 assert . ok ( await manager . create ( scriptUri ( 'trigger.py' ) ) ) ;
5923- assert . strictEqual ( await fs . pathExists ( stale . sysPrefix ) , false ) ;
5933+ assert . strictEqual ( await fs . pathExists ( orphan . sysPrefix ) , false ) ;
59245934 } ) ;
59255935 } ) ;
59265936
0 commit comments