Coverage integration for unusual JVM language

Answered

I'm interested in implementing coverage support for my plugin, but I'm not sure what the best way to proceed is. I've read the mega-thread here: https://intellij-support.jetbrains.com/hc/en-us/community/posts/206103879-Graphical-integration-of-running-tests-in-plugin?page=2, but I'm hoping to avoid implementing everything since my language (Clojure) is a JVM language. I looked at what Kotlin does, and it seems that they only need to register a JavaCoverageEngineExtension. I tried that and have breakpoints in my code and in JavaCoverageEngine, but they're not being hit. My extension is deemed applicable since my run configuration implements CommonJavaRunConfigurationParameters, and for example isSourceMapNeeded() is called.

I suspect that this problem is because Clojure is an unusual JVM language. It's not class-based, although obviously everything compiles to class files in the end. So my files don't implement PsiClassOwner, and my elements don't implement PsiClass. The names of the classes are also not deterministic from a particular source file, so I can't figure out for a particular PsiFile what the classes that will be produced are. It seems like the Java coverage stuff is all built around these assumptions. Many Clojure projects include code in Java or other JVM languages, so I'd like a coverage run to be able to annotate coverage from both Clojure and e.g. Java, so I don't want to reimplement all the coverage infrastructure as if it were a totally new language. Is there a way I can integrate Clojure without doing that?

7 comments
Comment actions Permalink

Hi Colin,

as Clojure is JVM based language you can reuse existing instrumenters which collect line coverage for the jvm classes. Can you get that work through existing JavaCoverageEngine? I guess you would need custom `com.intellij.coverage.JavaCoverageEngine#createCoverageViewExtension` as it shows package/class structure which is not applicable and you would need to override methods to get source/class mappings (I think, it must be possible as you have debugger, don't you?). Then if you register your inheritor of JavaCoverageEngine before default one, it should be used for your configurations.

I am sure there would be a lot of small problems here and there though in general it should somehow work

Anna

0
Comment actions Permalink

Thanks for the prompt response, Anna. I gave this a try - I created a ClojureCoverageEngine inheriting from JavaCoverageEngine, and a ClojureCoverageViewExension inheriting from JavaCoverageViewExtension. My plan with all these classes was to intercept the methods, check if it looks like the call is relevant to the Clojure portion of the user's code, and if not then delegate to the superclass so that the coverage will also work for their Java code. However I can't do this for the annotator - I tried to create a ClojureCoverageAnnotator extending JavaCoverageAnnotator, but that class is final. I then tried to use delegation, but that doesn't work because JavaCoverageAnnotator.createRenewRequest() is protected and also because I need a JavaCoverageAnnotator to pass to the JavaCoverageViewExtension constructor. I handled the second problem by passing the delegate to the constructor instead of my annotator, but that starts feeling pretty risky and I'm unsure of the implications. I also can't seem to fix the first problem - I can't just copy the code because it updates private data structures, and there's no good way for me to call it. Do you have any suggestions here?

0
Comment actions Permalink

I don't think you can get much useful stuff from `JavaCoverageViewExtension` or `JavaCoverageAnnotator` because they are connected to `PsiClass` too closely. So I'd use base classes instead. From the first glance there should be no problems with that.

Anna

0
Comment actions Permalink

The main thing I wanted to achieve with that was to be able to provide coverage info for Clojure and Java from the same run configuration, since Clojure projects are often polyglot JVM projects. If I don't use those classes at all then users will not see coverage for Java, correct?

0
Comment actions Permalink

Complicated. But generally that's true. What entries do you expect in coverage view?

0
Comment actions Permalink

In the coverage view I think users would expect to see namespace coverage (namespaces are also shown in the project view and would probably have coverage against them), and possibly function coverage too - these are roughly analogous to package and class coverage. My plan was to create my own classes but delegate to the Java ones if I can identify that the information doesn't relate to Clojure code. I'm not sure how what information comes back from the coverage agent yet, so perhaps my first step is to implement the Clojure-only coverage as you suggest and then to think about how to mix that presentation with the Java coverage, if that's even possible. It seems like the best thing would be to separate the class-based coverage info from e.g. Java and the namespace-based coverage from Clojure, perhaps into separate trees in the toolwindow. I have no idea if that's possible though.

0
Comment actions Permalink

From coverage agent you'll get ClassData objects which represent jvm classes with information about covered lines. If you are going to implement class wrappers, then probably inheritance would be your good choice indeed. As for JavaCoverageAnnotator: looks like you'll need it as is if you'll be able to map jvm classes to your wrappers.

0

Please sign in to leave a comment.