Skip to content

Commit 3b7cbf9

Browse files
committed
adding tests for helpers
1 parent 61745de commit 3b7cbf9

File tree

3 files changed

+406
-59
lines changed

3 files changed

+406
-59
lines changed

src/client/testing/testController/common/projectUtils.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,48 @@
44
import * as crypto from 'crypto';
55
import { PythonProject } from '../../../envExt/types';
66

7+
/**
8+
* Separator used to scope test IDs to a specific project.
9+
* Format: {projectId}{SEPARATOR}{testPath}
10+
* Example: "project-abc123def456::test_file.py::test_name"
11+
*/
12+
export const PROJECT_ID_SEPARATOR = '::';
13+
714
/**
815
* Generates a unique project ID by hashing the PythonProject object.
916
* This ensures consistent IDs across extension reloads for the same project.
17+
* Uses 16 characters of the hash to reduce collision probability.
1018
*
1119
* @param pythonProject The PythonProject object from the environment API
1220
* @returns A unique string identifier for the project
1321
*/
1422
export function generateProjectId(pythonProject: PythonProject): string {
1523
// Create a stable string representation of the project
24+
// Use URI as the primary identifier (stable across renames)
1625
const projectString = JSON.stringify({
17-
name: pythonProject.name,
1826
uri: pythonProject.uri.toString(),
27+
name: pythonProject.name,
1928
});
2029

2130
// Generate a hash to create a shorter, unique ID
31+
// Using 16 chars (64 bits) instead of 12 (48 bits) for better collision resistance
2232
const hash = crypto.createHash('sha256').update(projectString).digest('hex');
23-
return `project-${hash.substring(0, 12)}`;
33+
return `project-${hash.substring(0, 16)}`;
34+
}
35+
36+
/**
37+
* Parses a project-scoped vsId back into its components.
38+
*
39+
* @param vsId The VS Code test item ID to parse
40+
* @returns A tuple of [projectId, runId]. If the ID is not project-scoped,
41+
* returns [undefined, vsId] (legacy format)
42+
*/
43+
export function parseVsId(vsId: string): [string | undefined, string] {
44+
const separatorIndex = vsId.indexOf(PROJECT_ID_SEPARATOR);
45+
if (separatorIndex === -1) {
46+
return [undefined, vsId]; // Legacy ID without project scope
47+
}
48+
return [vsId.substring(0, separatorIndex), vsId.substring(separatorIndex + PROJECT_ID_SEPARATOR.length)];
2449
}
2550

2651
/**

src/client/testing/testController/controller.ts

Lines changed: 49 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import { ITestDebugLauncher } from '../common/types';
5252
import { PythonResultResolver } from './common/resultResolver';
5353
import { onDidSaveTextDocument } from '../../common/vscodeApis/workspaceApis';
5454
import { IEnvironmentVariablesProvider } from '../../common/variables/types';
55-
import { ProjectAdapter, WorkspaceDiscoveryState } from './common/projectAdapter';
55+
import { ProjectAdapter } from './common/projectAdapter';
5656
import { generateProjectId, createProjectDisplayName } from './common/projectUtils';
5757
import { PythonProject, PythonEnvironment } from '../../envExt/types';
5858
import { getEnvExtApi, useEnvExtension } from '../../envExt/api.internal';
@@ -73,17 +73,11 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
7373
// Map of workspace URI -> Map of project ID -> ProjectAdapter
7474
private readonly workspaceProjects: Map<Uri, Map<string, ProjectAdapter>> = new Map();
7575

76-
// Fast lookup maps for execution
77-
// @ts-expect-error - used when useProjectBasedTesting=true
78-
private readonly vsIdToProject: Map<string, ProjectAdapter> = new Map();
79-
// @ts-expect-error - used when useProjectBasedTesting=true
80-
private readonly fileUriToProject: Map<string, ProjectAdapter> = new Map();
81-
// @ts-expect-error - used when useProjectBasedTesting=true
82-
private readonly projectToVsIds: Map<string, Set<string>> = new Map();
83-
84-
// Temporary discovery state (created during discovery, cleared after)
85-
// @ts-expect-error - used when useProjectBasedTesting=true
86-
private readonly workspaceDiscoveryState: Map<Uri, WorkspaceDiscoveryState> = new Map();
76+
// TODO: Phase 3-4 - Add these maps when implementing discovery and execution:
77+
// - vsIdToProject: Map<string, ProjectAdapter> - Fast lookup for test execution
78+
// - fileUriToProject: Map<string, ProjectAdapter> - File watching and change detection
79+
// - projectToVsIds: Map<string, Set<string>> - Project cleanup and refresh
80+
// - workspaceDiscoveryState: Map<Uri, WorkspaceDiscoveryState> - Temporary overlap detection
8781

8882
private readonly triggerTypes: TriggerType[] = [];
8983

@@ -236,51 +230,49 @@ export class PythonTestController implements ITestController, IExtensionSingleAc
236230
// Try to use project-based testing if environment extension is enabled
237231
if (useEnvExtension()) {
238232
traceInfo('[test-by-project] Activating project-based testing mode');
239-
try {
240-
await Promise.all(
241-
Array.from(workspaces).map(async (workspace) => {
242-
traceInfo(`[test-by-project] Processing workspace: ${workspace.uri.fsPath}`);
243-
try {
244-
// Discover projects in this workspace
245-
const projects = await this.discoverWorkspaceProjects(workspace.uri);
246-
247-
// Create map for this workspace
248-
const projectsMap = new Map<string, ProjectAdapter>();
249-
projects.forEach((project) => {
250-
projectsMap.set(project.projectId, project);
251-
});
252-
253-
this.workspaceProjects.set(workspace.uri, projectsMap);
254-
255-
traceInfo(
256-
`[test-by-project] Discovered ${projects.length} project(s) for workspace ${workspace.uri.fsPath}`,
257-
);
258-
259-
// Set up file watchers if auto-discovery is enabled
260-
const settings = this.configSettings.getSettings(workspace.uri);
261-
if (settings.testing.autoTestDiscoverOnSaveEnabled) {
262-
traceVerbose(`Testing: Setting up watcher for ${workspace.uri.fsPath}`);
263-
this.watchForSettingsChanges(workspace);
264-
this.watchForTestContentChangeOnSave();
265-
}
266-
} catch (error) {
267-
traceError(
268-
`[test-by-project] Failed to activate project-based testing for ${workspace.uri.fsPath}:`,
269-
error,
270-
);
271-
traceInfo('[test-by-project] Falling back to legacy mode for this workspace');
272-
// Fall back to legacy mode for this workspace
273-
await this.activateLegacyWorkspace(workspace);
274-
}
275-
}),
276-
);
277-
return;
278-
} catch (error) {
279-
traceError(
280-
'[test-by-project] Failed to activate project-based testing, falling back to legacy mode:',
281-
error,
282-
);
283-
}
233+
234+
// Use Promise.allSettled to allow partial success in multi-root workspaces
235+
const results = await Promise.allSettled(
236+
Array.from(workspaces).map(async (workspace) => {
237+
traceInfo(`[test-by-project] Processing workspace: ${workspace.uri.fsPath}`);
238+
239+
// Discover projects in this workspace
240+
const projects = await this.discoverWorkspaceProjects(workspace.uri);
241+
242+
// Create map for this workspace
243+
const projectsMap = new Map<string, ProjectAdapter>();
244+
projects.forEach((project) => {
245+
projectsMap.set(project.projectId, project);
246+
});
247+
248+
traceInfo(
249+
`[test-by-project] Discovered ${projects.length} project(s) for workspace ${workspace.uri.fsPath}`,
250+
);
251+
252+
return { workspace, projectsMap };
253+
}),
254+
);
255+
256+
// Handle results individually - allows partial success
257+
results.forEach((result, index) => {
258+
const workspace = workspaces[index];
259+
if (result.status === 'fulfilled') {
260+
this.workspaceProjects.set(workspace.uri, result.value.projectsMap);
261+
traceInfo(
262+
`[test-by-project] Successfully activated ${result.value.projectsMap.size} project(s) for ${workspace.uri.fsPath}`,
263+
);
264+
this.setupFileWatchers(workspace);
265+
} else {
266+
traceError(
267+
`[test-by-project] Failed to activate project-based testing for ${workspace.uri.fsPath}:`,
268+
result.reason,
269+
);
270+
traceInfo('[test-by-project] Falling back to legacy mode for this workspace');
271+
// Fall back to legacy mode for this workspace only
272+
this.activateLegacyWorkspace(workspace);
273+
}
274+
});
275+
return;
284276
}
285277

286278
// Legacy activation (backward compatibility)

0 commit comments

Comments
 (0)