22// Licensed under the MIT License.
33
44import { CancellationToken , FileCoverageDetail , TestItem , TestRun , TestRunProfileKind , TestRunRequest } from 'vscode' ;
5- import { traceError , traceInfo , traceVerbose } from '../../../logging' ;
5+ import { traceError , traceInfo , traceVerbose , traceWarn } from '../../../logging' ;
66import { sendTelemetryEvent } from '../../../telemetry' ;
77import { EventName } from '../../../telemetry/constants' ;
88import { IPythonExecutionFactory } from '../../../common/process/types' ;
@@ -71,7 +71,7 @@ export async function executeTestsForProjects(
7171 traceInfo ( `[test-by-project] Executing ${ items . length } test item(s) for project: ${ project . projectName } ` ) ;
7272
7373 sendTelemetryEvent ( EventName . UNITTEST_RUN , undefined , {
74- tool : 'pytest' ,
74+ tool : project . testProvider ,
7575 debugging : isDebugMode ,
7676 } ) ;
7777
@@ -100,10 +100,7 @@ export async function executeTestsForProjects(
100100}
101101
102102/**
103- * Lookup context for project resolution during a single test run.
104- * Maps file paths to their resolved ProjectAdapter to avoid
105- * repeated API calls and linear searches.
106- * Created fresh per run and discarded after grouping completes.
103+ * Lookup context for avoiding redundant API calls within a single test run.
107104 */
108105interface ProjectLookupContext {
109106 /** Maps file URI fsPath → resolved ProjectAdapter (or undefined if no match) */
@@ -113,15 +110,8 @@ interface ProjectLookupContext {
113110}
114111
115112/**
116- * Groups test items by their owning project using the Python Environment API.
117- * Each test item's URI is matched to a project via the API's getPythonProject method.
118- * Falls back to path-based matching when the extension API is not available.
119- *
120- * Uses a per-run cache to avoid redundant API calls for test items sharing the same file.
121- *
122- * Time complexity: O(n + p) amortized, where n = test items, p = projects
123- * - Building adapter lookup map: O(p)
124- * - Each test item: O(1) amortized (cached after first lookup per unique file)
113+ * Groups test items by their owning project. Uses env API when available, else falls back to path matching.
114+ * Time complexity: O(n + p) amortized via per-run caching.
125115 */
126116export async function groupTestItemsByProject (
127117 testItems : TestItem [ ] ,
@@ -134,7 +124,8 @@ export async function groupTestItemsByProject(
134124 result . set ( getProjectId ( project . projectUri ) , { project, items : [ ] } ) ;
135125 }
136126
137- // Build lookup context for this run - O(p) setup, enables O(1) lookups
127+ // Build lookup context for this run - O(p) one-time setup, enables O(1) lookups per item.
128+ // When tests are from a single project, most lookups hit the cache after the first item.
138129 const lookupContext : ProjectLookupContext = {
139130 uriToAdapter : new Map ( ) ,
140131 projectPathToAdapter : new Map ( projects . map ( ( p ) => [ p . projectUri . fsPath , p ] ) ) ,
@@ -150,7 +141,7 @@ export async function groupTestItemsByProject(
150141 }
151142 } else {
152143 // If no project matches, log it
153- traceVerbose ( `[test-by-project] Could not match test item ${ item . id } to a project` ) ;
144+ traceWarn ( `[test-by-project] Could not match test item ${ item . id } to a project` ) ;
154145 }
155146 }
156147
@@ -165,12 +156,7 @@ export async function groupTestItemsByProject(
165156}
166157
167158/**
168- * Finds the project that owns a test item based on the test item's URI.
169- * Uses the Python Environment extension API when available, falling back
170- * to path-based matching (longest matching path prefix).
171- *
172- * Results are stored in the lookup context to avoid redundant API calls for items in the same file.
173- * Time complexity: O(1) amortized with context, O(p) worst case on context miss.
159+ * Finds the project that owns a test item. Uses env API when available, else path-based matching.
174160 */
175161export async function findProjectForTestItem (
176162 item : TestItem ,
@@ -188,7 +174,9 @@ export async function findProjectForTestItem(
188174
189175 let result : ProjectAdapter | undefined ;
190176
191- // Try using the Python Environment extension API first
177+ // Try using the Python Environment extension API first.
178+ // Legacy path: when useEnvExtension() is false, this block is skipped and we go
179+ // directly to findProjectByPath() below (path-based matching).
192180 if ( useEnvExtension ( ) ) {
193181 try {
194182 const envExtApi = await getEnvExtApi ( ) ;
@@ -206,7 +194,8 @@ export async function findProjectForTestItem(
206194 }
207195 }
208196
209- // Fallback: path-based matching (most specific/longest path wins)
197+ // Fallback: path-based matching when env API unavailable or didn't find a match.
198+ // O(p) time complexity where p = number of projects.
210199 if ( ! result ) {
211200 result = findProjectByPath ( item , projects ) ;
212201 }
@@ -220,8 +209,7 @@ export async function findProjectForTestItem(
220209}
221210
222211/**
223- * Finds the project that owns a test item using path-based matching.
224- * Returns the most specific (longest path) matching project.
212+ * Fallback: finds project using path-based matching. O(p) time complexity.
225213 */
226214function findProjectByPath ( item : TestItem , projects : ProjectAdapter [ ] ) : ProjectAdapter | undefined {
227215 if ( ! item . uri ) return undefined ;
0 commit comments