Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use tree sitter for getting the node id (aka test id) for the function and class at point #75

Merged
merged 20 commits into from
Aug 26, 2024

Conversation

rodrigomorales1
Copy link
Contributor

@rodrigomorales1 rodrigomorales1 commented Aug 12, 2024

The master branch uses the function python-pytest--current-defun for getting the test id of the function at point or class at point and it supports at most 2 parts. The changes in this pull request adds two functions: python-pytest--node-id-def-at-point (link) and python-pytest--node-id-class-at-point (link) which get the path for the def or class at point for an arbitrary number of nested classes, these two functions use tree-sitter. See the file tests/test-python-helpers.el (link to file) for examples on how these functions behave. By having two functions for different purposes, the user has more control over what to execute (e.g. (s)he could execute a Test class when the point is on a function).

Please let me know what you think of these changes, so that I can improve this pull request to suit more use cases.

We rename the parameter to improve the readability of the code because
the function python-pytest--run will be used for executing specific
functions and classes in future commits and each function or class is
identified by their node id. The term "node id" is used in the
official pytest documentation:
https://docs.pytest.org/en/7.1.x/how-to/usage.html#nodeids
We need to create a new version because in future commits we will mark
a function as obsolete since this new version.
The new functions that should be used python-pytest--path-def-at-point
are python-pytest--path-class-at-point.

python-pytest--current-defun could obtain the identifiers for
functions and classes. If the current point was inside a function,
there was no way to get the test id of the current class. By using
python-pytest--path-def-at-point and
python-pytest--path-class-at-point, there is no such limitation.
The new functions that should be used are python-pytest-run-def-at-point
and python-pytest-run-class-at-point.
@rodrigomorales1
Copy link
Contributor Author

rodrigomorales1 commented Aug 12, 2024

I just noticed that I have used both terms "path" and "node id" in function names and docstrings for referring to the same thing. I think it would be better to use the same terminology used in the pytest documentation, that is to use "node-id" instead of "path"

Retrieved from https://docs.pytest.org/en/8.3.x/example/markers.html#node-id

Node IDs are of the form module.py::class::method or module.py::function. Node IDs control which tests are collected, so module.py::class will select all test methods on the class. Nodes are also created for each parameter of a parametrized fixture or test, so selecting a parametrized test must include the parameter value, e.g. module.py::function[param].

Retrieved from https://docs.pytest.org/en/8.3.x/example/markers.html#selecting-tests-based-on-their-node-id

You can provide one or more node IDs as positional arguments to select only specified tests. This makes it easy to select tests based on their module, class, method, or function name:

Give me a few minutes to make another commit.

@pkryger
Copy link
Contributor

pkryger commented Aug 13, 2024

With a caveat that I haven't read all code changes yet, I'd like to rise one point.

I think that the change is great, and I believe using treesit is the way to go in a future. However, the package (python-pytest) is marked as Emacs-24 compatible and, IIRC, treesit has only been available since Emacs-29. Would this change work in older Emacs? Perhaps some compatibility layer would be needed (I doubt that compat has treesit, but maybe worth checking)? Alternatively, perhaps the minimum version required should be updated?

@wbolster
Copy link
Owner

same concerns here!

i'm all for using more modern and more reliable ways to do syntactical heavy lifting, but i don't want to break this package for people who are using reasonably modern operating systems (e.g. ubuntu lts).

that said, emacs 24 is essentially a random number that was perhaps relevant at some point for some compat reason, but that version is from the 2012–2015 era (release history) and i don't care at all about ancient software. (if you do, good for you, but then also use this package from that time… which didn't exist 🙃)

@wbolster wbolster added enhancement New feature or request help wanted Extra attention is needed labels Aug 15, 2024
python-pytest.el Outdated Show resolved Hide resolved
@pkryger
Copy link
Contributor

pkryger commented Aug 16, 2024

Moving discussion about minimum required Emacs back to main thread.

... but i don't want to break this package for people who are using reasonably modern operating systems (e.g. ubuntu lts).

I agree. I didn't consider versions that don't have tree-sitter when I did the previous commits, but now I think we should consider them.

I think I could add a conditional in the transient so that it displays the new functions if the user is using GNU Emacs that supports tree-sitter (i.e. >= 29). Users that don't have tree-sitter support would continue using python-pytest--current-defun, and users that have tree-sitter would use python-pytest--node-id-def-at-point and python-pytest--node-id-class-at-point.

I think this (a compatibility layer) was something I have been trying to suggest in my original comment. The only nit pick would be - as I was once told - the recommended solution is to use some predicate (i.e., one of functionp/packagep/featurep) in such an if block. IMHO an implementation like that is slightly harder to maintain (than an explicit version check) in longer period. Especially when minimum supported Emacs version is meaningful: it requires a mental mapping between supported functionality and Emacs versions. I tend to add comments with minimal version in places like that.

Last but not least, you’d probably want to add (require treesit nil t) stanza to the beginning of the main package file.

In a recent previous commit, python-pytest--current-defun had been
marked as obsolete and all function calls have been replaced in favour
of python-pytest--node-id-def-at-point. However, we should continue
supporting python-pytest--current-defun so that users that use a GNU
Emacs version that don't have tree-sitter support can continue using
this package. See comment:
wbolster#75 (comment)

To make it clear, Emacs users that have tree-sitter support should use
python-pytest--node-id-def-at-point and
python-pytest--node-id-class-at-point. Emacs users that don't have
tree-sitter support should use python-pytest--current-defun and those
functions that call that function (i.e. python-pytest-function and
python-pytest-run-def-at-point-dwim).
In a recent previous commit, we have added 2 functions:
python-pytest--node-id-def-at-point-treesit and
python-pytest--node-id-class-at-point-treesit. These two function
names clearly explain that they either get the node id of the def at
or class at point.

The name python-pytest--current-defun can be confusing for some
readers because the reader might think that it can only get the def at
point, not the class at point. The new name
python-pytest--node-id-def-or-class-at-point makes it clear that it
can get both the def or class at point.
The functions python-pytest-function and
python-pytest-run-def-at-point-dwim call
python-pytest--node-id-def-or-class-at-point. We rename those 2
functions for better readibility.
@rodrigomorales1
Copy link
Contributor Author

rodrigomorales1 commented Aug 16, 2024

In the recently introduced commits ...

@pkryger @wbolster Please let me know what you think.

Copy link
Contributor

@pkryger pkryger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks good to me, and (almost) ready to merge I haven't read it in great detail (and I don't think I will have time to in next few days 😟).

I think the only missing thing is (require 'treesit nil t) in the beginning of the file (around l. 32)

python-pytest.el Outdated
@@ -127,6 +127,16 @@ When non-nil only ‘test_foo()’ will match, and nothing else."
(set-default symbol value)
value))))

(defcustom python-pytest-use-treesit nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about

Suggested change
(defcustom python-pytest-use-treesit nil
(defcustom python-pytest-use-treesit (featurep 'treesit)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason to NOT use treesitter even though it's available? i'd rather have less configuration and more ‘dwim’ behaviour, and not having this customizable at all would have my preference.

Copy link
Contributor Author

@rodrigomorales1 rodrigomorales1 Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason to NOT use treesitter even though it's available?

@wbolster There are some differences between the functions in main branch and the functions that use treesit. Users that still prefer the behavior in main branch could set the variable python-pytest-use-treesit to nil. I suggest that we have a custom variable to provide users more control on how the package works.

Difference 1: Depths of classes

In main branch, the functions python-pytest-function (renamed to python-pytest-run-def-or-class-at-point in a commit in this pull request) and python-pytest-function-dwim (renamed to python-pytest-run-def-or-class-at-point-dwim in a previous commit in this pull request) both use python-pytest--current-defun (renamed to python-pytest--node-id-def-or-class-at-point in a previous commit in this pull request).

In main branch, python-pytest--current-defun gets the current function or class at point up to a second depth. The new function python-pytest--node-id-class-at-point-treesit get the class at point for an arbitrary depth. The new function python-pytest--node-id-def-at-point-treesit gets the def at point for the outermost function (because pytest doesn't allow grouping tests using functions inside functions, it only allows using classes inside classes). See example below.

class ClassDepthOne:
  class ClassDepthTwo:
    class ClassDepthThree:
      def print_foo(self):
        def print_bar():<point>
          print("foo")
ELISP> (python-pytest--current-defun)
"ClassDepthOne::ClassDepthTwo"
ELISP> (python-pytest--node-id-class-at-point-treesit)
"ClassDepthOne::ClassDepthTwo::ClassDepthThree"
ELISP> (python-pytest--node-id-def-at-point-treesit)
"ClassDepthOne::ClassDepthTwo::ClassDepthThree::print_foo"

There are some differences between the functions with the suffix -treesit and the functions in main branch. Users that still prefer the behavior in main branch, could use

Difference 2: dwim behavior

In main branch, the function python-pytest-function-dwim search for a test file and for test names when the current file is not a test file (see below). There is no function that use treesit and implement such behavior.

file (python-pytest--sensible-test-file file)
func (python-pytest--make-test-name func))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pkryger I think the only missing thing is (require 'treesit nil t) in the beginning of the file (around l. 32)

Done, see 1f29d55

@pkryger How about

(defcustom python-pytest-use-treesit (featurep 'treesit)

Done, see e09e8f8

Copy link
Contributor

@pkryger pkryger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

@rodrigomorales1
Copy link
Contributor Author

rodrigomorales1 commented Aug 23, 2024

In the latest commit, I improved *-treesit functions so that they can correctly handle narrowed buffers. I also added tests when the buffer is narrowed.

@wbolster wbolster merged commit 9390f9f into wbolster:main Aug 26, 2024
@wbolster
Copy link
Owner

lgtm 🚀

@wbolster
Copy link
Owner

merged, thanks @rodrigomorales1 and @pkryger!

hlissner pushed a commit to doomemacs/doomemacs that referenced this pull request Oct 3, 2024
These two commands were renamed upstream and pulled in in 1fa1eba:

- python-pytest-function-dwim -> python-pytest-run-def-or-class-at-point
- python-pytest-function-dwim -> python-pytest-run-def-or-class-at-point-dwim

Amend: 1fa1eba
Ref: wbolster/emacs-python-pytest#75
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request help wanted Extra attention is needed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants