2

I recently had this very annoying problem come out of nowhere. Running my unit tests with EclEmma coverage enabled causes the following dialog window to pop up from Eclipse:

enter image description here

For the search engines, it reads:

No coverage data has been collected during this coverage Session.
Please do not terminate the Java process manually from Eclipse.

No coverage information is provided for any of the classes in my project. Needless to say I am not terminating the Java process manually. To try and fix it I: reimported my project, upgraded Java, reinstalled Emma, restarted my Macbook Pro, made sure that temp filesystem space looked good, and 20 other things I'm forgetting right now.

I finally noticed that it was only a couple of my open source projects generating this error and decided to whittle down one of my tests. Here's the minimum test that reproduces the problem.

Test class I'm trying to get coverage on:

public class Foo {
    public void method() {
        System.out.println("hello");
    }
}

Here's the junit class which drives it:

public class EclEmmaFailureTest {
    @Test(timeout = 100000) // if you remove the timeout it works
    public void testStuff() {
        // this should cover Foo 100%
        new Foo().method();
        // if you comment this out stuff works
        org.apache.commons.logging.LogFactory.getLog(getClass());
    }
}

The commons-log Log reference in the test seems to break the coverage collection. I've posted a working repo at: https://github.com/j256/eclemma-failure

If you do any of the following, the problem goes away:

  • Comment out the LogFactory.getLog(getClass()) call.
  • Remove the junit @Test timeout field.
  • Downgrade Junit from 4.13.1 to 4.12.

I'm running Emma version 3.1.3 and my test program depends on commons-logging version 1.2.

I have a downgrade path that resolves this problem and gets me working again but Junit 4.12 has security issues. I'm curious if someone knows of the specific issue with junit or emma that is causing this.

Jacoco is also affected which isn't surprising. Here's my coverage report before upgrading to Junit 4.13.1 showing 80% coverage and here it is afterwards with no coverage information available showing 0%.

Thanks.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • This is similar but not the same as: https://stackoverflow.com/questions/37212214/coverage-fatal-error-eclemma/42904311 – Gray Dec 30 '20 at 17:44

2 Answers2

3

No coverage data has been collected during this coverage Session. Please do not terminate the Java process manually from Eclipse.

This seems to be a Junit problem around threadgroups that was introduced in version 4.13. See this github discussion and see this pull request. When a timeout was set, it seems that Junit had been destroying the threadgroup to handle stuck threads issues which was not giving Eclemma a chance to write out the coverage information. They seem to have changed the thread-group to be daemon threads as a better way to handle this issue.

Seems like the only solution is going to have to wait for 4.13.2 to be released since versions before 4.13.1 suffer from a security issue.

Looks like shutdown hooks are not executed the same way in 4.13 as it used to happen in 4.12 and thus the coverage failure.

Thanks to the Eclemma mailing list for their help on this.

Gray
  • 115,027
  • 24
  • 293
  • 354
0

EMMA is not used here, even if the name EclEmma might imply it. In fact, EclEmma started in 2006 as an Eclipse integration of EMMA. But more than 9 years ago, since EclEmma 2.0, EMMA has been replaced by JaCoCo, a free code coverage library for Java created by the EclEmma team.

Since a code change in the application and/or in the test makes the problem go away, it is very unlikely that the coverage data is collected but not displayed. Therefore, the only likely remaining reason is that something is interfering with JaCoCo collecting the data. The FAQ of JaCoCo names what that might be:

Why does a class show as not covered although it has been executed?

First make sure execution data has been collected. For this select the Sessions link on the top right corner of the HTML report and check whether the class in question is listed. If it is listed but not linked the class at execution time is a different class file. Make sure you're using the exact same class file at runtime as for report generation. Note that some tools (e.g. EJB containers, mocking frameworks) might modify your class files at runtime. Please see the chapter about class ids for a detailed discussion.

To make sure it's not a caching problem, try if also a minor code change makes the problem go away as well.

The things you list that make the problem go away are very different, but all might affect the timing, which would indicate a concurrency issue. You might try to change the order of the tests and/or add Thread.sleep() at some places to see if that changes anything.

However, in your case the root cause is unclear without having minimal reproducible example (that might be difficult to provide, if it is a concurrency issue).

Update:

As Evgeny Mandrikov pointed out, the root problem is indeed a concurrency issue of JUnit 4.13 and 4.13.1 (including all 4.13-beta-* and 4.13-rc-* versions, but previous versions of JUnit are not affected):

JUnit 4 issue #1652: Timeout ThreadGroups should not be destroyed

The issue has already been fixed for the upcoming JUnit 4.13.2 release.

The following can be used as a workaround to prevent the thread group from being destroyed and thus JaCoCo loosing its collected data (by adding a dummy thread into that group):

/** Workaround for already fixed JUnit 4 issue #1652 (<https://github.com/junit-team/junit4/issues/1652>) */
public static void workaround_for_junit4_issue_1652() {
    String version = junit.runner.Version.id();
    if (!"4.13.1".equals(version) && !"4.13".equals(version))
        fail("Workaround for JUnit 4 issue #1652 required for JUnit 4.13 and 4.13.1 only; actual version: "
           + version);
    Thread thread = Thread.currentThread();
    if (!"Time-limited test".equals(thread.getName())) fail("Workaround only required for a test with a timeout.");
    new Thread(thread.getThreadGroup(), new Runnable() {
        @Override
        public void run() {
            try {
                while (!thread.isInterrupted()) Thread.sleep(100);
            } catch (InterruptedException e) {}
        }
    }).start();
}

@Test(timeout = 42)
public void test() {
    workaround_for_junit4_issue_1652();
    // ...
}
howlger
  • 31,050
  • 11
  • 59
  • 99
  • Thanks for the answer. It's not that I'm not seeing my class @howlger, it's that it spews the error message in question. – Gray Dec 30 '20 at 18:28
  • The point is that JaCoCo does not see the mocked class. – howlger Dec 30 '20 at 18:32
  • That's fine but I'd like to see my test class and any other classes in my project. My failure is not missing classes but an inability to get any coverage data. – Gray Dec 30 '20 at 18:34
  • Sure, the problem here is that in this case with mocking leads to JaCoCo collects no coverage data for these classes. Follow the [_"class ids"_](https://www.eclemma.org/jacoco/trunk/doc/classids.html) link and read the _"What workarounds exist to deal with runtime-modified classes?"_ section for what you can do on EclEmma side. If this does not help, try to mock less or use a different mocking framework. – howlger Dec 30 '20 at 20:41
  • Sorry @howlget but this doesn't answer my question. It doesn't explain the error message I'm getting. I'm not expecting mocked classes to have coverage data. – Gray Jan 02 '21 at 19:19
  • The classes you expect coverage data from are not direct or indirect referenced by the mocked classes? Please provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – howlger Jan 02 '21 at 19:25
  • @Gray It is unclear to me what you are trying to achieve by the downvote instead of answering my questions. I'm here to help (see my extended answer), not to gather reputation points. What exactly do you mean by "Jacoco is also affected"? Do you use JaCoCo also in your code or in a dependency? – howlger Jan 03 '21 at 13:07
  • I downvoted the answer because it's not helpful @howlger. Isn't that how it's supposed to work? This is not a mocking issue. This is not a caching issue. You've not attempted to explain the error I'm getting. I keep on pointing that out and you keep on ignoring my answers to your questions. And it _is_ EclEmma according to their branding: https://www.eclemma.org/ – Gray Jan 03 '21 at 18:53
  • And I did provide a MRE @howlger. See my post. I've just fleshed it out a bit and posted it on github. https://github.com/j256/eclemma-failure – Gray Jan 03 '21 at 19:14
  • My answer says that something is interfering with JaCoCo and that your observations indicate that's a concurrency issue, which turns out to be true @Gray. With an MRE it would have been possible by replacing `createMock(...)` with `Thread.sleep()` (as proposed by my answer) to prove that your question is not related to EasyMock. You "answered" the question for more details about how mocking is used and for a MRE with a downvote. But I am glad that it works now @Gray. – howlger Jan 04 '21 at 10:48
  • Replacing the `EasyMock` call with `Thread.sleep()` would have removed the issue @howlger as my example mentioned. I don't quite understand you other comments. Why did you feel the need to copy my answer into your's? – Gray Jan 04 '21 at 16:12
  • Nope, not for me; a test with timeout that just calls e.g. `LogFactory.getLog(...)` is enough to reproduce this issue; mocking is not needed at all. @Gray, I have not copied your answer into mine. You created your own answer instead of just add the link of the JUnit issue (containing a detailed explanation by [Godin](https://stackoverflow.com/users/244993/godin) who, by the way, probably didn't see your question here about _Emma_ instead of about _EclEmma_ and _JaCoCo_) to my existing answer. – howlger Jan 04 '21 at 20:16
  • Oh good simplification. I've made that change. Why would I add the correct answer into your answer? – Gray Jan 05 '21 at 14:50
  • How can this question and your question before be read as anything other than passive-aggressive? What exactly do you want me to do? – howlger Jan 05 '21 at 16:06
  • I certainly think that it is improper for you to copy from my answer into your's as an "update". I guess I want you to consider removing the update. Probably doesn't matter but it seems like really bad form. And I'm asking seriously, why would you think that I should edit your answer so I can add the correct details to it? – Gray Jan 06 '21 at 17:38
  • @Gray, I just added Godin's reference to the JUnit issue to my answer as an update since it proves that it was indeed a concurrency issue (which was something where one could not be sure without having the code). Your answer concludes _"the only solution is going to have to wait for 4.13.2"_ whereas my answer provides a workaround which is the opposite of your answer, isn't it? – howlger Jan 07 '21 at 02:07