@@ -129,23 +129,13 @@ func (s *Runtime) Test(ctx context.Context, req *runtimev0.TestRequest) (*runtim
129129 // plugin is the ONLY place the generic provisioning map becomes uv flags.
130130 if cmd , output , env , prov , ok := resolveTestFormula (req , s .Base .Service , s .Service .SourceLocation ); ok {
131131 fspec := pythonhelpers .SpecFromFormula (cmd , output , env , prov , nil )
132- selectors , selectorErr := pythonTestRequestSelectors (req , fspec .Command , fspec .Cwd )
133- if selectorErr != nil {
132+ if selectorErr := applyPythonFormulaTestScope (req , & fspec ); selectorErr != nil {
134133 return nil , fmt .Errorf ("python runtime selection: %w" , selectorErr )
135134 }
136- // A typed selector is authoritative execution scope. Derived pytest
137- // commands may carry broad project discovery operands (for example,
138- // `pytest --pyargs astropy docs {posargs}`). Appending one node ID to
139- // those operands still collects unrelated modules, allowing their
140- // failures to masquerade as the selected case. Keep the real runner
141- // prefix and replace broad pytest discovery before adding the exact
142- // plugin-rendered selector.
143- if req .GetSelection () != nil {
144- fspec .Command = pythonhelpers .CommandForExactSelection (fspec .Command )
145- }
146- fspec .Selectors = selectors
147135 s .Wool .Info ("running test formula via uv" ,
148- wool .Field ("command" , fspec .Command ), wool .Field ("output" , fspec .Output ))
136+ wool .Field ("command" , fspec .Command ),
137+ wool .Field ("selectors" , fspec .Selectors ),
138+ wool .Field ("output" , fspec .Output ))
149139 fStart := time .Now ()
150140 fRun , fErr := pythonhelpers .RunFormulaStructured (ctx , s .Service .SourceLocation , fspec )
151141 evidence := pythonhelpers .RuntimeEvidenceForFormula (s .Service .SourceLocation , cmd , output , env , prov , formulaDerivedFromProject (req , s .Base .Service ))
@@ -368,18 +358,83 @@ func boundedTestLogMessage(message string) string {
368358 ) + message [tailStart :]
369359}
370360
371- // pythonTestRequestSelectors translates authoritative typed scope inside the
372- // Python plugin. Legacy target/filter requests remain supported for interactive
373- // broad runs, but never participate in the typed acknowledgement contract.
374- func pythonTestRequestSelectors (req * runtimev0.TestRequest , command []string , cwd string ) ([]string , error ) {
361+ // applyPythonFormulaTestScope translates the language-neutral runtime request
362+ // into runner-private formula arguments. This is the boundary that owns the
363+ // distinction between pytest collection targets and pytest name expressions:
364+ // callers provide target/filter data and never construct runner flags.
365+ //
366+ // ARCHITECTURE: A target or exact pytest node identity is authoritative scope.
367+ // Derived formulas may contain broad discovery operands such as
368+ // `pytest --pyargs astropy docs`; retaining those operands would execute
369+ // unrelated tests in addition to the requested target. Exact scope therefore
370+ // replaces broad discovery, while ordinary filters preserve the derived scope
371+ // and become pytest's name-expression argument. Typed TestSelection remains
372+ // the acknowledged, fail-closed acceptance contract.
373+ func applyPythonFormulaTestScope (req * runtimev0.TestRequest , spec * pythonhelpers.TestFormulaSpec ) error {
375374 if req .GetSelection () != nil {
376- return pythonhelpers .RenderTestSelection (req .GetSelection (), command , cwd )
375+ selectors , err := pythonhelpers .RenderTestSelection (req .GetSelection (), spec .Command , spec .Cwd )
376+ if err != nil {
377+ return err
378+ }
379+ spec .Command = pythonhelpers .CommandForExactSelection (spec .Command )
380+ spec .Selectors = selectors
381+ return nil
382+ }
383+
384+ // JUnit XML selects Codefly's pytest adapter. The Python plugin, not Mind,
385+ // owns pytest's private distinction between positional collection targets
386+ // and -k name expressions.
387+ if spec .Output == pythonhelpers .OutputJUnitXML {
388+ var exactSelectors , nameFilters []string
389+ for _ , filter := range req .GetFilters () {
390+ if isExactPytestSelector (filter ) {
391+ exactSelectors = append (exactSelectors , filter )
392+ } else if strings .TrimSpace (filter ) != "" {
393+ nameFilters = append (nameFilters , filter )
394+ }
395+ }
396+ if target := strings .TrimSpace (req .GetTarget ()); target != "" {
397+ exactSelectors = append (exactSelectors , target )
398+ }
399+ if len (exactSelectors ) > 0 {
400+ spec .Command = pythonhelpers .CommandForExactSelection (spec .Command )
401+ }
402+ if len (nameFilters ) > 0 {
403+ spec .ExtraArgs = append (spec .ExtraArgs , "-k" , strings .Join (nameFilters , " or " ))
404+ // A filter-only request is a complete test execution even though it
405+ // has no positional selector. Say so explicitly: formula Core's auto
406+ // mode intentionally treats a selector-free invocation as an
407+ // environment materialization probe.
408+ spec .ExecutionMode = pythonhelpers .FormulaExecutionComplete
409+ }
410+ spec .Selectors = exactSelectors
411+ return nil
412+ }
413+
414+ // Non-pytest formula runners own their positional selector grammar. Core's
415+ // formula executor performs the runner-specific normalization (for example,
416+ // unittest display identities to Django dotted labels).
417+ spec .Selectors = append (spec .Selectors , req .GetFilters ()... )
418+ if target := strings .TrimSpace (req .GetTarget ()); target != "" {
419+ spec .Selectors = append (spec .Selectors , target )
420+ }
421+ return nil
422+ }
423+
424+ // isExactPytestSelector recognizes collection identities, not arbitrary name
425+ // expressions. This knowledge belongs in the Python runtime plugin. SWE-bench
426+ // node IDs and file targets are positional pytest operands; simple names and
427+ // boolean expressions remain filters.
428+ func isExactPytestSelector (value string ) bool {
429+ value = strings .TrimSpace (value )
430+ if value == "" {
431+ return false
377432 }
378- selectors := append ([]string (nil ), req .GetFilters ()... )
379- if target := req .GetTarget (); target != "" {
380- selectors = append (selectors , target )
433+ if strings .Contains (value , "::" ) {
434+ return true
381435 }
382- return selectors , nil
436+ normalized := strings .ReplaceAll (value , `\` , "/" )
437+ return strings .HasSuffix (normalized , ".py" ) || strings .Contains (normalized , ".py/" )
383438}
384439
385440func pythonDefaultTestScope (req * runtimev0.TestRequest ) (string , []string , error ) {
0 commit comments