Skip to content

Commit 250cb4d

Browse files
authored
Merge branch 'main' into crucial-elephant
2 parents 025d16a + a06de4b commit 250cb4d

File tree

11 files changed

+914
-33
lines changed

11 files changed

+914
-33
lines changed

.github/instructions/testing-workflow.instructions.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,3 +602,7 @@ envConfig.inspect
602602
- Never create "documentation tests" that just `assert.ok(true)` — if mocking limitations prevent testing, either test a different layer that IS mockable, or skip the test entirely with a clear explanation (1)
603603
- When stubbing vscode APIs in tests via wrapper modules (e.g., `workspaceApis`), the production code must also use those wrappers — sinon cannot stub properties directly on the vscode namespace like `workspace.workspaceFolders`, so both production and test code must reference the same stubbable wrapper functions (4)
604604
- **Before writing tests**, check if the function under test calls VS Code APIs directly (e.g., `commands.executeCommand`, `window.createTreeView`, `workspace.getConfiguration`). If so, FIRST update the production code to use wrapper functions from `src/common/*.apis.ts` (create the wrapper if it doesn't exist), THEN write tests that stub those wrappers. This prevents CI failures where sinon cannot stub the vscode namespace (4)
605+
- **Async initialization in `setImmediate` must have error handling**: When extension activation uses `setImmediate(async () => {...})` for async manager registration, wrap it in try-catch with proper error logging. Unhandled errors cause silent failures where managers never register, making smoke/E2E tests hang forever (1)
606+
- **Never skip tests to hide infrastructure problems**: If tests require native binaries (like `pet`), the CI workflow must build/download them. Skipping tests when infrastructure is missing gives false confidence. Build from source (like vscode-python does) rather than skipping. Tests should fail clearly when something is wrong (2)
607+
- **No retries for masking flakiness**: Mocha `retries` should not be used to mask test flakiness. If a test is flaky, fix the root cause. Retries hide real issues and slow down CI (1)
608+
- **pet binary is required for environment manager registration**: The smoke/E2E/integration tests require the `pet` binary from `microsoft/python-environment-tools` to be built and placed in `python-env-tools/bin/`. Without it, `waitForApiReady()` will timeout because managers never register. CI must build pet from source using `cargo build --release --package pet` (2)

.github/workflows/pr-check.yml

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,49 @@ jobs:
8888
- name: Checkout
8989
uses: actions/checkout@v4
9090

91+
- name: Checkout Python Environment Tools
92+
uses: actions/checkout@v4
93+
with:
94+
repository: 'microsoft/python-environment-tools'
95+
path: 'python-env-tools-src'
96+
sparse-checkout: |
97+
crates
98+
Cargo.toml
99+
Cargo.lock
100+
sparse-checkout-cone-mode: false
101+
102+
- name: Install Rust Toolchain
103+
uses: dtolnay/rust-toolchain@stable
104+
105+
- name: Cache Rust build
106+
uses: actions/cache@v4
107+
with:
108+
path: |
109+
~/.cargo/registry
110+
~/.cargo/git
111+
python-env-tools-src/target
112+
key: ${{ runner.os }}-cargo-pet-${{ hashFiles('python-env-tools-src/Cargo.lock') }}
113+
restore-keys: |
114+
${{ runner.os }}-cargo-pet-
115+
116+
- name: Build Python Environment Tools
117+
run: cargo build --release --package pet
118+
working-directory: python-env-tools-src
119+
120+
- name: Copy pet binary (Unix)
121+
if: runner.os != 'Windows'
122+
run: |
123+
mkdir -p python-env-tools/bin
124+
cp python-env-tools-src/target/release/pet python-env-tools/bin/
125+
chmod +x python-env-tools/bin/pet
126+
127+
- name: Copy pet binary (Windows)
128+
if: runner.os == 'Windows'
129+
run: |
130+
mkdir -p python-env-tools/bin
131+
cp python-env-tools-src/target/release/pet.exe python-env-tools/bin/
132+
shell: bash
133+
91134
- name: Install Node
92135
uses: actions/setup-node@v4
93136
with:
@@ -138,6 +181,49 @@ jobs:
138181
- name: Checkout
139182
uses: actions/checkout@v4
140183

184+
- name: Checkout Python Environment Tools
185+
uses: actions/checkout@v4
186+
with:
187+
repository: 'microsoft/python-environment-tools'
188+
path: 'python-env-tools-src'
189+
sparse-checkout: |
190+
crates
191+
Cargo.toml
192+
Cargo.lock
193+
sparse-checkout-cone-mode: false
194+
195+
- name: Install Rust Toolchain
196+
uses: dtolnay/rust-toolchain@stable
197+
198+
- name: Cache Rust build
199+
uses: actions/cache@v4
200+
with:
201+
path: |
202+
~/.cargo/registry
203+
~/.cargo/git
204+
python-env-tools-src/target
205+
key: ${{ runner.os }}-cargo-pet-${{ hashFiles('python-env-tools-src/Cargo.lock') }}
206+
restore-keys: |
207+
${{ runner.os }}-cargo-pet-
208+
209+
- name: Build Python Environment Tools
210+
run: cargo build --release --package pet
211+
working-directory: python-env-tools-src
212+
213+
- name: Copy pet binary (Unix)
214+
if: runner.os != 'Windows'
215+
run: |
216+
mkdir -p python-env-tools/bin
217+
cp python-env-tools-src/target/release/pet python-env-tools/bin/
218+
chmod +x python-env-tools/bin/pet
219+
220+
- name: Copy pet binary (Windows)
221+
if: runner.os == 'Windows'
222+
run: |
223+
mkdir -p python-env-tools/bin
224+
cp python-env-tools-src/target/release/pet.exe python-env-tools/bin/
225+
shell: bash
226+
141227
- name: Install Node
142228
uses: actions/setup-node@v4
143229
with:
@@ -188,6 +274,49 @@ jobs:
188274
- name: Checkout
189275
uses: actions/checkout@v4
190276

277+
- name: Checkout Python Environment Tools
278+
uses: actions/checkout@v4
279+
with:
280+
repository: 'microsoft/python-environment-tools'
281+
path: 'python-env-tools-src'
282+
sparse-checkout: |
283+
crates
284+
Cargo.toml
285+
Cargo.lock
286+
sparse-checkout-cone-mode: false
287+
288+
- name: Install Rust Toolchain
289+
uses: dtolnay/rust-toolchain@stable
290+
291+
- name: Cache Rust build
292+
uses: actions/cache@v4
293+
with:
294+
path: |
295+
~/.cargo/registry
296+
~/.cargo/git
297+
python-env-tools-src/target
298+
key: ${{ runner.os }}-cargo-pet-${{ hashFiles('python-env-tools-src/Cargo.lock') }}
299+
restore-keys: |
300+
${{ runner.os }}-cargo-pet-
301+
302+
- name: Build Python Environment Tools
303+
run: cargo build --release --package pet
304+
working-directory: python-env-tools-src
305+
306+
- name: Copy pet binary (Unix)
307+
if: runner.os != 'Windows'
308+
run: |
309+
mkdir -p python-env-tools/bin
310+
cp python-env-tools-src/target/release/pet python-env-tools/bin/
311+
chmod +x python-env-tools/bin/pet
312+
313+
- name: Copy pet binary (Windows)
314+
if: runner.os == 'Windows'
315+
run: |
316+
mkdir -p python-env-tools/bin
317+
cp python-env-tools-src/target/release/pet.exe python-env-tools/bin/
318+
shell: bash
319+
191320
- name: Install Node
192321
uses: actions/setup-node@v4
193322
with:

.vscode-test.mjs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export default defineConfig([
1111
mocha: {
1212
ui: 'tdd',
1313
timeout: 120000,
14-
retries: 1,
1514
},
1615
env: {
1716
VSC_PYTHON_SMOKE_TEST: '1',
@@ -32,7 +31,6 @@ export default defineConfig([
3231
mocha: {
3332
ui: 'tdd',
3433
timeout: 180000,
35-
retries: 1,
3634
},
3735
env: {
3836
VSC_PYTHON_E2E_TEST: '1',
@@ -51,7 +49,6 @@ export default defineConfig([
5149
mocha: {
5250
ui: 'tdd',
5351
timeout: 60000,
54-
retries: 1,
5552
},
5653
env: {
5754
VSC_PYTHON_INTEGRATION_TEST: '1',

src/extension.ts

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -515,34 +515,44 @@ export async function activate(context: ExtensionContext): Promise<PythonEnviron
515515
* Below are all the contributed features using the APIs.
516516
*/
517517
setImmediate(async () => {
518-
// This is the finder that is used by all the built in environment managers
519-
const nativeFinder: NativePythonFinder = await createNativePythonFinder(outputChannel, api, context);
520-
context.subscriptions.push(nativeFinder);
521-
const sysMgr = new SysPythonManager(nativeFinder, api, outputChannel);
522-
sysPythonManager.resolve(sysMgr);
523-
await Promise.all([
524-
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr),
525-
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
526-
registerPyenvFeatures(nativeFinder, context.subscriptions, projectManager),
527-
registerPipenvFeatures(nativeFinder, context.subscriptions, projectManager),
528-
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
529-
shellStartupVarsMgr.initialize(),
530-
]);
531-
532-
await applyInitialEnvironmentSelection(envManagers, projectManager, nativeFinder, api);
533-
534-
// Register manager-agnostic terminal watcher for package-modifying commands
535-
registerTerminalPackageWatcher(api, terminalActivation, outputChannel, context.subscriptions);
536-
537-
// Register listener for interpreter settings changes for interpreter re-selection
538-
context.subscriptions.push(
539-
registerInterpreterSettingsChangeListener(envManagers, projectManager, nativeFinder, api),
540-
);
518+
try {
519+
// This is the finder that is used by all the built in environment managers
520+
const nativeFinder: NativePythonFinder = await createNativePythonFinder(outputChannel, api, context);
521+
context.subscriptions.push(nativeFinder);
522+
const sysMgr = new SysPythonManager(nativeFinder, api, outputChannel);
523+
sysPythonManager.resolve(sysMgr);
524+
await Promise.all([
525+
registerSystemPythonFeatures(nativeFinder, context.subscriptions, outputChannel, sysMgr),
526+
registerCondaFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
527+
registerPyenvFeatures(nativeFinder, context.subscriptions, projectManager),
528+
registerPipenvFeatures(nativeFinder, context.subscriptions, projectManager),
529+
registerPoetryFeatures(nativeFinder, context.subscriptions, outputChannel, projectManager),
530+
shellStartupVarsMgr.initialize(),
531+
]);
532+
533+
await applyInitialEnvironmentSelection(envManagers, projectManager, nativeFinder, api);
534+
535+
// Register manager-agnostic terminal watcher for package-modifying commands
536+
registerTerminalPackageWatcher(api, terminalActivation, outputChannel, context.subscriptions);
537+
538+
// Register listener for interpreter settings changes for interpreter re-selection
539+
context.subscriptions.push(
540+
registerInterpreterSettingsChangeListener(envManagers, projectManager, nativeFinder, api),
541+
);
541542

542-
sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime);
543-
await terminalManager.initialize(api);
544-
sendManagerSelectionTelemetry(projectManager);
545-
await sendProjectStructureTelemetry(projectManager, envManagers);
543+
sendTelemetryEvent(EventNames.EXTENSION_MANAGER_REGISTRATION_DURATION, start.elapsedTime);
544+
await terminalManager.initialize(api);
545+
sendManagerSelectionTelemetry(projectManager);
546+
await sendProjectStructureTelemetry(projectManager, envManagers);
547+
} catch (error) {
548+
traceError('Failed to initialize environment managers:', error);
549+
// Show a user-friendly error message
550+
window.showErrorMessage(
551+
l10n.t(
552+
'Python Environments: Failed to initialize environment managers. Some features may not work correctly. Check the Output panel for details.',
553+
),
554+
);
555+
}
546556
});
547557

548558
sendTelemetryEvent(EventNames.EXTENSION_ACTIVATION_DURATION, start.elapsedTime);

0 commit comments

Comments
 (0)