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

Provide filtering and JVM method level detail for test classes #653

Open
1 of 4 tasks
Arthurm1 opened this issue Feb 12, 2024 · 3 comments
Open
1 of 4 tasks

Provide filtering and JVM method level detail for test classes #653

Arthurm1 opened this issue Feb 12, 2024 · 3 comments

Comments

@Arthurm1
Copy link

Describe the use-case for this feature

Method detail...
When opening up a java/scala file containing a test class, a BSP client only knows that the class itself can be run as a test but ideally it would know what individual methods can be run.

e.g. in Java

import org.junit.jupiter.api.Test;

public class TestClass {
    @Test
    public void testFoo() { 
        // some code
    }
    @Test
    public void testBar() { 
        // some code
    }
}

In responding to buildTarget/jvmTestEnvironment, the BSP server will supply "TestClass" in its list of test mainClasses but will provide no information about testFoo or testBar. If a client such as Metals wants to let the user run these individual methods it has to be aware of the various test frameworks out there (junit, scalatest, munit etc.) to know how to query the file to identify test methods.

I think that the client shouldn't have to know any details about the test framework being used and the BSP server should supply the test methods as well as test classes.

Filtering...
Along with returning test methods, it would be probably improve performance if the buildTarget/jvmTestEnvironment request could include some kind of filtering. At the moment it returns all test classes for a list of build targets. If a user creates/changes a file/class then the BSP client has to re-request the test classes for the entire build target rather than restrict that to the document just created/changed.

What do you propose

Method detail...
Currently JvmEnvironmentItem is returned when the BSP server responds to buildTarget/jvmRunEnvironment and to buildTarget/jvmTestEnvironment.

JvmEnvironmentItem contains an array of JvmMainClass which contains a className.

Proposed options...

  1. The BSP Server could simply treat className as though it was classAndMethodName and return TestClass#testFoo, TestClass#testBar. This is not very clear to the anyone implementing/using the API but could be just a change to documentation.

  2. JvmMainClass could be extended to contain methodNames? : string[].
    This wouldn't make much sense for a buildTarget/jvmRunEnvironment request but it could be left blank or could be always main.

  3. methods?: JvmMethods[] could be added to JvmEnvironmentItem where JvmMethod would contain a classname and an array of method names.
    Again this wouldn't make much sense in response to buildTarget/jvmRunEnvironment. It would also be partially duplicating the JvmMainClass contents.

The above options would have minimal effect on current BSP clients.

  1. Create a JvmTestEnvironmentItem class and use that as a response to buildTarget/jvmTestEnvironment. Unfortunately, unlike other parts of the BSP API, the data is not set here using data and dataKind so it would probably mean that an entirely new buildTarget/jvmTestEnvironment2 would have to be created which would be painful for current users.

One thing I'm not sure on with all the above proposals: is the method name alone enough to distinguish different test methods? I'm not familiar with many test frameworks but JUnit allows parameterization of test methods so someone could theoretically define tests as...

    @ParameterizedTest
    void testFoo(int param1) {}
    @ParameterizedTest
    void testFoo(int param1, int param2) {}

or just have additional non test methods of the same name...

    void testFoo(int i) {}
    @Test
    void testFoo() {
        testFoo(1);
        testFoo(2);
    }

So would the method's signature have to be supplied as well as its name to distinguish the 2 methods in the above cases?

Currently I'm favouring proposal 2. If method signatures are needed then methodNames?: string could become methods?: JvmMethod.

Filtering...
If test methods were added to the spec then another thing that would be nice to have is a way to filter what classes should be returned.

If the test file has been changed and saved, the BSP client could requery the server for test classes/methods purely for that file/class.

Proposed change...

  1. Add textDocument?: TextDocumentIdentifier[] to JvmTestEnvironmentParams so the BSP server can internally query only the required documents for new or changed test methods.

  2. Add classNames?: string[] to JvmTestEnvironmentParams so the BSP server can internally query only the required documents for new or changed test methods.

  3. Allow both 1) and 2) types of filtering by using data/dataKind to filter and then also hopefully future proof for different types of filtering.

I favour proposal 2. I think proposal 1 would be ideal for BSP clients but I don't think test frameworks generally allow filtering by file. Instead they filter by package/class/method.
I also can't see why a BSP client would need anything as fine as Method name filtering to discover new tests. I would have thought fully qualified classname is granular enough

I've checked to make sure there isn't already a way to support this in the current protocol

  • I've checked the existing protocol and there isn't a way to do this currently

Maintainer approval (This is for the maintainers)

  • Jetbrains
  • Metals
  • Scala Center
@tgodzik
Copy link
Collaborator

tgodzik commented Feb 20, 2024

For the first part I don't think we would actually be able to do everything in the build server. Some test frameworks are easy to discover single test cases for, while other like munit or utest require actually running the test class to find them. I also don't think we have support for test cases in the code that discovers test cases, so that would need to be reimplemented for all the possible framework.

For Metals (and for Intellij I guess) we use a lot of parsing etc. so this might not be even possible on the BSP server side. The only thing we could do is add a separate library to not have to do it twice.

As for filtering it would be interesting to have only the new/changed/removed classes sent, though filtering by package not to query everything in the target might also be useful, but are you having an issue with this currently somwhere?

@Arthurm1
Copy link
Author

The reason for this is that I was looking to implement buildTarget/jvmTestEnvironment on the Gradle build server but there is no way to pass back framework like with buildTarget/scalaTestClasses, so Metals then doesn't know the framework and can't do test method discovery. I guess in this case Metals could run discovery for all of its known test frameworks? I don't know the performance implications of this.

I see the various finders in Metals have been written but to me that code should be in the test libraries themselves and the frameworks that require running to discover tests would supply a dry-run option so discovery can be done. Then all of this should be pushed down to the build server with minimal code per supported test framework.

This is possible with JUnit's test discovery. I had hoped that all test frameworks would have something similar.

I guess this request isn't really worth it if test discovery just isn't offered by most test frameworks.

The filtering request was because I thought there might be performance issues if the build server was scanning for all methods on all classes.

@tgodzik
Copy link
Collaborator

tgodzik commented Feb 20, 2024

I guess in this case Metals could run discovery for all of its known test frameworks? I don't know the performance implications of this.

That's possible, it shouldn't be too bad since we can do it after compilation. We could use the same thing as sbt and Bloop.

I see the various finders in Metals have been written but to me that code should be in the test libraries themselves and the frameworks that require running to discover tests would supply a dry-run option so discovery can be done. Then all of this should be pushed down to the build server with minimal code per supported test framework.

The current test discovery doesn't take into account test cases, so this would be a lot of work to add. It also works on the classfile level, not on that actual source level, so this would need to be greatly reworked. It's much easier to do it in Metals.

This is possible with JUnit's test discovery. I had hoped that all test frameworks would have something similar.

I think sbt uses something different, or possibly the default junit discovery underneath. We could improve the Metals finder using it.

I guess this request isn't really worth it if test discovery just isn't offered by most test frameworks.

Not as far as I know, only class level.

The filtering request was because I thought there might be performance issues if the build server was scanning for all methods on all classes.

I haven't seen it being an issue, but on some larger projects it might be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants