-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Macro Code Coverage #14880
Comments
I started on an prototype implementation of this and have something that seems like it's working quite well. However, the exact design of this feature is still slightly up in the air. A primary use case of this feature will be, for me at least, to run against spec files to ensure the test coverage I have against my macro logic is sufficient and not missing any paths. Unlike the Because of this we have a few options on how to best enable the generation of the coverage report:
I'm currently leaning towards the ENV var, just because it seems to fit my use case the best. But def interested in hearing other's use cases, reasoning for another option, or entirely new options. |
An important characteristic of macro code is that it's not only determined by other code, but macros are often used to respond to compiler flags. Depending on the build target or explicit configuration flags, you get different control flow in macros. In order to fully test a piece of macro functionality that responds to flags, you may need to run it with a bunch of different flag combinations. For that use case it should be possible to easily run only macro coverage for little overhead (i.e. without codegen). That may hint towards a separate tool ( |
This is pretty much my use case yea. I don't really have any logic based on compiler flags (that is embedded in the macro logic at least). The logic that is behind flags is tested by running the normal specs on different OSs.
For this, would it be easy enough to just pass |
Another con of the ENV var is there wouldn't be a good way to control what gets included in the report. I.e. the Also will need a way to handle terminal nodes, like |
Okay after playing with this more, I have another possible solution for the entry point. I realized that my concern with it being a tool may not be entirely true. Running the tool itself does essentially what However to fully support this, the tool would need (working initial thoughts):
Does this sound reasonable? Otherwise, where is everyone leaning in regards how to expose this feature? |
annotation Test; end
@[Test(id: 10)]
record Foo
module MyModule
macro included
macro finished
{% verbatim do %}
{%
pp Foo.annotation(Test)[:id] * 10
%}
{% end %}
end
end
end
class Container
include MyModule
end
Container.new It seems we do indeed need to handle pp! node,
f.source,
location,
location.macro_location,
f.macro.location,
location.expanded_location node # => pp((Foo.annotation(Test))[:id] * 10)
f.source # => " macro finished\n" +
" \n" +
" {% pp((Foo.annotation(Test))[:id] * 10) %}\n" +
" \n" +
" end\n" +
" "
location # => expanded macro: included:3:12
location.macro_location # => /home/george/dev/git/crystal/cov.cr:7:3
f.macro.location # => /home/george/dev/git/crystal/cov.cr:7:3
location.expanded_location # => /home/george/dev/git/crystal/cov.cr:19:3 There doesn't seem to be a direct way to have it point to the actual location of the node, which would be line Any ideas, or is this something that needs to be added to the compiler? |
So as an update on my progress, the current state of things is master...Blacksmoke16:crystal:macro-code-coverage. It's very hacky, prob less than ideal code at the moment, but all specs pass and it serves its purpose as a prototype. I was able to handle: macro finished
{% verbatim do %}
{% 10 * 10 %}
{%
20 * 20
30 * 30
%}
{% end %}
end By adding However, one of the only contexts it currently doesn't handle is: macro finished
{% verbatim do %}
{%
10 * 10
# Foo
10 * 20
%}
{% end %}
end Because the empty new line and So would love to know if anyone has any ideas on how to handle this. |
Only hacky idea I could think of so far is like having a way to:
From here, this should make the lines match what's in the source, which should allow me to calculate proper real line numbers. Not really sure how to go about this tho... |
I realized that we have access to the original filename in the source. So it's possible to open that file, read the lines, and find the one that includes the node's source in it. I'm not sure how robust this is if more than one line happens to include the same string representation of another node later one, it would always use the first. This does suggest that it might be possible to get something working tho, possibly might be able to include this in the |
So I pushed up some more commits to my test branch: master...Blacksmoke16:crystal:macro-code-coverage. It's very hacky but seems to be working most of the time for macro hooks and multi-line The main problem now is when when doing the check of IDK if there's a good way around that other than more holistically providing some better way to get at the real line number natively. But maybe it's "good enough" to just require people to use EDIT: Maybe one thing that would help me understand this is say you have the following code: macro finished
{% verbatim do %}
{%
10 * 10
# Foo
10 * 20
%}
{% end %}
end The macro interpreter can access each of the 4 numbers as Would it be possible to add like |
One aspect of code coverage that is not currently handled via
kcov
and such, is that of compile time code coverage for macros. Being able to know if there are unused code paths within conditional macro logic would be really helpful. Mainly given there wouldn't be an error until it's hit if there was a bug/typo within that branch. It would be great if Crystal could generate some sort of simple coverage file for this to make use of the same tooling. E.g. something along the lines of https://docs.codecov.com/docs/codecov-custom-coverage-format.Related: #1157
The text was updated successfully, but these errors were encountered: