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

Add preliminary support for code coverage #692

Merged
merged 13 commits into from
Mar 26, 2019

Conversation

andyscott
Copy link
Contributor

@andyscott andyscott commented Feb 16, 2019

This adds preliminary support for code coverage with Jacoco.

This will likely require some iteration before it's extremely robust. Ideally some of that iteration can happen in follow up work in separate PRs, since the code in this PR should mostly suffice for Scala centric projects.

I haven't tested this on any production codebases yet.

@andyscott
Copy link
Contributor Author

This looks like it's just flat out failing with the older version of Bazel. Hopefully the 0.22.0 build passes.

@johnynek
Copy link
Member

@softprops you did some work on this in the past, right? Do you have any time to weigh in on the PR?

@andyscott
Copy link
Contributor Author

andyscott commented Feb 16, 2019

I should add that I chose to implement this slightly different compared to the Java rules:
A provider is used to "swap out" regular artifacts for instrumented artifacts when they're finally needed. The intention was to be as lazy as possible, and avoid any risk of recompilation of Scala artifacts.
Conversely, the Java rules simply emit an instrumented artifact when coverage is turned on.

@johnynek
Copy link
Member

@andyscott can you give a quick overview of the approach? I did a quick scan, but I haven't been following the bazel coverage support. Can you help reviewers by providing some links for us to read on how that is supposed to work?

I'm very excited about this!

@andyscott
Copy link
Contributor Author

andyscott commented Feb 16, 2019

As far as I can tell, there's no public documentation on how code coverage works internally in Bazel. This implementation was based off of information I gleaned from:

It's currently EOD for me. I can circle back next week and hopefully provide more info.

@ittaiz
Copy link
Member

ittaiz commented Feb 16, 2019 via email

@ittaiz
Copy link
Member

ittaiz commented Feb 16, 2019

I skimmed the code and I’m somewhat worried about the cost of the aspect. @dslomov is there a way to achieve something similar to the aspect declaration on the attribute from command line? I tried using the regular syntax but it’s not the same

@ittaiz
Copy link
Member

ittaiz commented Feb 16, 2019

One idea (which I’m fairly certain is possible) is to add a flag to the scala tool chain and only do work in the aspect if the flag is on. This limits the memory part and probably most of the analysis perf cost (not sure how much an aspect with a lookup and empty array return costs)

@andyscott
Copy link
Contributor Author

It's pretty easy to remove the aspect and just conditionally emit an instrumented jar as the primary artifact.

@johnynek
Copy link
Member

Here is a recent PR on coverage in the haskell rules: tweag/rules_haskell#699

@dslomov
Copy link
Contributor

dslomov commented Feb 18, 2019

Neither the usage of aspects not providers in this PR should add much to the memory footprint (it is O(size of build graph) and should not be much)
What might be problematic is any flattening of depsets but I do not see it occurring here.

@andyscott
Copy link
Contributor Author

andyscott commented Feb 26, 2019

Update: This PR works for us internally at Stripe. There also seems to be an issue with the reproducibility tests for the changes, which I'll look into.

Once that's taken care of, what steps do I need to take to get this to a mergeable state?

Copy link
Member

@johnynek johnynek left a comment

Choose a reason for hiding this comment

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

This is very exciting and one of the biggest feature additions in quite some time. Very nice work.

That said, I'd like to suggest two core changes:

  1. split the PR into three PRs:
  2. a formatting only PR that improves the formatting, but has no other changes. I assume you used some tool to format it. Can we do that on master?
  3. can we add this tools/bazel wrapper and a separate motivation for it.
  4. just the changes relating to code coverage with a minimum amount of formatting change.
  5. I request that you go ahead as part 3. include documentation in the README as to how generate code coverage reports, a brief explanation that there should be no additional cost to running tests in normal mode, and also give some indication of how much longer you should expect the tests to take with coverage enabled.

Thanks for taking this on.

PR: @ittaiz I think you will want to take a good look at this. I assume you all may be interested in providing code coverage reports. I love to use them to make sure I am hitting all the branches in my code in tests, without which I get nervous I have bugs lurking.

"exports",
]

def _combine(*entriess, base = {}):
Copy link
Member

Choose a reason for hiding this comment

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

why entriess? Any meaning there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just because it's a 2d array of entry items (or a 1d array of entries). In the comprehension I have:

[
    entry[_CoverageReplacements].replacements
    for entries in entriess
    for entry in entries
    if _CoverageReplacements in entry
]

scala/scala.bzl Outdated Show resolved Hide resolved
tools/bazel Outdated Show resolved Hide resolved
scala/private/rule_impls.bzl Outdated Show resolved Hide resolved
@andyscott
Copy link
Contributor Author

andyscott commented Mar 1, 2019 via email

@andyscott
Copy link
Contributor Author

This should be good to go-- it passes on 0.22.0. A fair amount of this functionality just won't work on older Bazel releases.

@andyscott
Copy link
Contributor Author

All tests are passing now with the exception of the reproducibility tests, due to JMH.

Copy link
Member

@johnynek johnynek left a comment

Choose a reason for hiding this comment

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

I think this looks good. @ittaiz do you have any concerns?

We have run this on our CI, and it worked fine for us.

}

@Override
public void processRequest(List < String > args) {
Copy link
Member

Choose a reason for hiding this comment

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

weird spacing on the List<String> here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can fix it. Not sure how it wound up this way (perhaps my editor's Java formatter got carried away?).

Copy link
Member

@ittaiz ittaiz left a comment

Choose a reason for hiding this comment

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

Any chance we can add a flag for this in the toolchain?
I have a bad feeling on the overhead of these loops in the aspect and I'd love a way to turn it off if I'm right. Should be simple to add. WDYT?

)

filegroup(
name = "instrumenter_files",
Copy link
Member

Choose a reason for hiding this comment

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

Is this indirection needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it's just a relic from the original work to develop this code. I can remove it.

Copy link
Member

Choose a reason for hiding this comment

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

should you remove it?

tools/bazel Outdated Show resolved Hide resolved

rjars = out.transitive_rjars

coverage_runfiles = []
Copy link
Member

Choose a reason for hiding this comment

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

This will be needed for junior test as well, right? Is there commonality? Can/should ewe reuse?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by junior test?

Copy link
Member

Choose a reason for hiding this comment

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

Junit (damn autocorrect)

Copy link
Member

Choose a reason for hiding this comment

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

Do I need to implement them for junit to have coverage?

@johnynek
Copy link
Member

johnynek commented Mar 6, 2019

can you merge master? I was trying to get this green by rerunning the flakey test. hasn't happened yet.

@johnynek
Copy link
Member

@andyscott my understanding is that this does not change runtime of build, is that accurate?

I'm in favor of merge. @ittaiz do you have any objections?

@andyscott
Copy link
Contributor Author

It doesn't change the runtime.

For what it's worth, I'm part of the way through feature flagging the dictionary merge loops. I should have that commit ready soon.

@andyscott
Copy link
Contributor Author

@johnynek @ittaiz 👋 just a friendly bump on this one

@beala-stripe
Copy link
Contributor

Eager to see this one merged. Thanks everyone!

@johnynek
Copy link
Member

This looks great. Good work.

I'd like to merge. I'll give @ittaiz 12 more hours to speak up if he or Wix have any concerns.

Thanks for making it configurable (although I do have concerns we have underdocumented how to use all our different options here, and that is hurting adoption).

Copy link
Member

@ittaiz ittaiz left a comment

Choose a reason for hiding this comment

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

Thank you for this work and especially for adding the configurability! I appreciate it.
Do y'all have some metrics on how this impacted (if any) your build performance? (analysis time, execution time, etc).
Also I replied to two small existing comments you didn't resolve but since I've been blocking this for a while we can merge as is and either ignore them or do a followup for them.
Your decision.

)

filegroup(
name = "instrumenter_files",
Copy link
Member

Choose a reason for hiding this comment

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

should you remove it?


rjars = out.transitive_rjars

coverage_runfiles = []
Copy link
Member

Choose a reason for hiding this comment

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

Do I need to implement them for junit to have coverage?

@johnynek johnynek merged commit 379c65e into bazelbuild:master Mar 26, 2019
@johnynek
Copy link
Member

Thanks @ittaiz I’ll let @andyscott follow up tomorrow but merge so we can iterate.

Also Andy, it would be nice to make sure the README has good docs on how to use this. And also a blog post would be nice to consider Andy.

@ittaiz
Copy link
Member

ittaiz commented Mar 26, 2019 via email

@andyscott andyscott deleted the code-coverage branch March 31, 2019 05:38
@andyscott
Copy link
Contributor Author

Just saw these comments. I'll follow up this work week!

@softprops
Copy link
Contributor

Is there any useful README documentation for getting started with rules_scala and code coverage or is the best place to discover that to look for coverage interfaces in the upstream bazel docs?

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

Successfully merging this pull request may close these issues.

7 participants