python: Fix identification of runnable tests within decorated test classes (#29688)

Ben Kunkle created

Closes #29486

Release Notes:

- python: Fixed identification of runnable test functions within
decorated pytest classes

Change summary

crates/languages/src/python/runnables.scm | 99 ++++++++++++++++--------
1 file changed, 65 insertions(+), 34 deletions(-)

Detailed changes

crates/languages/src/python/runnables.scm 🔗

@@ -4,12 +4,12 @@
         name: (identifier) @run @_unittest_class_name
         superclasses: (argument_list
             [(identifier) @_superclass
-            (attribute (identifier) @_superclass)]
-        )
+                (attribute (identifier) @_superclass)]
+            )
         (#eq? @_superclass "TestCase")
-    ) @_python-unittest-class
+        ) @_python-unittest-class
     (#set! tag python-unittest-class)
-)
+    )
 
 ; test methods whose names start with `test` in a TestCase
 (
@@ -17,18 +17,18 @@
         name: (identifier) @_unittest_class_name
         superclasses: (argument_list
             [(identifier) @_superclass
-            (attribute (identifier) @_superclass)]
-        )
+                (attribute (identifier) @_superclass)]
+            )
         (#eq? @_superclass "TestCase")
         body: (block
-                (function_definition
-                    name: (identifier) @run @_unittest_method_name
-                    (#match? @_unittest_method_name "^test.*")
+            (function_definition
+                name: (identifier) @run @_unittest_method_name
+                (#match? @_unittest_method_name "^test.*")
                 ) @_python-unittest-method
-                (#set! tag python-unittest-method)
+            (#set! tag python-unittest-method)
             )
         )
-)
+    )
 
 ; pytest functions
 (
@@ -36,10 +36,10 @@
         (function_definition
             name: (identifier) @run @_pytest_method_name
             (#match? @_pytest_method_name "^test_")
-        ) @_python-pytest-method
-    )
+            ) @_python-pytest-method
+        )
     (#set! tag python-pytest-method)
-)
+    )
 
 ; decorated pytest functions
 (
@@ -53,7 +53,8 @@
             ) @_python-pytest-method
         )
     (#set! tag python-pytest-method)
-)
+    )
+
 
 ; pytest classes
 (
@@ -61,47 +62,77 @@
         (class_definition
             name: (identifier) @run @_pytest_class_name
             (#match? @_pytest_class_name "^Test")
-        )
+            )
         (#set! tag python-pytest-class)
+        )
     )
-)
 
-; pytest class methods
+
+; decorated pytest classes
 (
     (module
-        (class_definition
-            name: (identifier) @_pytest_class_name
-            (#match? @_pytest_class_name "^Test")
-            body: (block
-                    (function_definition
-                        name: (identifier) @run @_pytest_method_name
-                        (#match? @_pytest_method_name "^test")
-                    ) @_python-pytest-method
-                    (#set! tag python-pytest-method)
+        (decorated_definition
+            (decorator)+ @_decorator
+            definition: (class_definition
+                name: (identifier) @run @_pytest_class_name
+                (#match? @_pytest_class_name "^Test")
+                )
             )
+        (#set! tag python-pytest-class)
         )
     )
-)
 
-; decorated pytest class methods
+
+; pytest class methods
 (
     (module
         (class_definition
             name: (identifier) @_pytest_class_name
             (#match? @_pytest_class_name "^Test")
             body: (block
-                (decorated_definition
+                [(decorated_definition
                     (decorator)+ @_decorator
                     definition: (function_definition
                         name: (identifier) @run @_pytest_method_name
                         (#match? @_pytest_method_name "^test_")
                         )
                     )
-            ) @_python-pytest-method
+                (function_definition
+                    name: (identifier) @run @_pytest_method_name
+                    (#match? @_pytest_method_name "^test")
+                    )
+                ] @_python-pytest-method)
             (#set! tag python-pytest-method)
+            )
+        )
+    )
+
+; decorated pytest class methods
+(
+    (module
+        (decorated_definition
+            (decorator)+ @_decorator
+            definition: (class_definition
+                name: (identifier) @_pytest_class_name
+                (#match? @_pytest_class_name "^Test")
+                body: (block
+                    [(decorated_definition
+                        (decorator)+ @_decorator
+                        definition: (function_definition
+                            name: (identifier) @run @_pytest_method_name
+                            (#match? @_pytest_method_name "^test_")
+                            )
+                        )
+                    (function_definition
+                        name: (identifier) @run @_pytest_method_name
+                        (#match? @_pytest_method_name "^test")
+                        )
+                    ] @_python-pytest-method)
+                (#set! tag python-pytest-method)
+                )
+            )
         )
     )
-)
 
 ; module main method
 (
@@ -111,10 +142,10 @@
                 (identifier) @run @_lhs
                 operators: "=="
                 (string) @_rhs
-            )
+                )
             (#eq? @_lhs "__name__")
             (#match? @_rhs "^[\"']__main__[\"']$")
             (#set! tag python-module-main-method)
+            )
         )
     )
-)