8

This question suggests to use the timeout parameter of the @Test annotation to have JUnit forcefully stop tests after that timeout period.

But we have like 5000 unit tests so far, and we want to establish a policy that asks developers to never release tests that need more than 10 seconds to complete. The policy would probably say "aim for < 10 seconds", but then we would like to ensure that any test is stopped after say 30 seconds. (the numbers are just examples, the idea is to define something that is "good enough" for most use cases, but that also makes sure things dont run "forever" )

Now I am wondering if there is a way to enable such behavior without turning into each test case and adding that annotation parameter.

The existing question doesn't help either: I am looking for one change to enable this, not a one change per test class solution. One central, global switch. Not one per file or method.

GhostCat
  • 137,827
  • 25
  • 176
  • 248
  • 1
    You can add a ``@Rule`` annotation (https://github.com/junit-team/junit4/wiki/timeout-for-tests), which is valid for a whole tested class rather than an individual method. It looks like someone already had your problem too: https://github.com/junit-team/junit4/issues/140 – f1sh May 08 '18 at 08:27
  • Possible duplicate of [What's the best way to set up a per-test or per-class timeout when using in perBatch forkmode?](https://stackoverflow.com/questions/8743594/whats-the-best-way-to-set-up-a-per-test-or-per-class-timeout-when-using-junit) – f1sh May 08 '18 at 08:27
  • @f1sh Which reduces to *one* change per file, still a lot given hundreds of test classes. Which is **not** what I asked for. – GhostCat May 08 '18 at 08:28
  • 1
    yes. I don't have any other idea :/ – f1sh May 08 '18 at 08:31
  • 1
    I don't believe you will find "one timeout to rule them all" since JUnit is about small test cases, each cases have execution time that differ. But in the worst case, I would go for @f1sh solution, this will be better to change 100 files than 5000 methods. In a month or two you will be able to ask students to do it ;) – AxelH May 08 '18 at 08:32
  • With JUnit Jupiter (a.k.a., JUnit 5), it is possible to register an extension globally (i.e., via Java's `ServiceLoader` mechanism) that would fail tests that take too long to execute. However, such an extension could not (currently) preemptively terminate test execution. I could post such an _answer_, but since you claim to want to ensure "things dont run _forever_"... I'm not sure you'd be interested. – Sam Brannen May 08 '18 at 12:06
  • @SamBrannen I think this gets closer to an answer as anything else. And I am an rapid upvoter, so why dont you just try? ;) – GhostCat May 08 '18 at 12:08
  • 1
    FYI: we are _considering_ adding support for global timeouts in JUnit Jupiter: https://github.com/junit-team/junit5/issues/80 – Sam Brannen May 08 '18 at 12:09
  • probably a good question, but those 10 seconds *could* mean other reason than actual run time, what if GC kicks in on that machine with a stop-the-world event? just sayin... I would strongly disagree with such rules IFF it were my decision – Eugene May 08 '18 at 14:31
  • @Eugene Valid point, but I guess we would do quite some "dry" runs before really enabling the whole thing. And of course, be rather lenient on the numbers to use. – GhostCat May 08 '18 at 14:36
  • @GhostCat so you are OK sacrificing correctness over speed? Not exactly this way, but just trying to spoil your party here... :) I would care less on how much *correctly* written unit tests run. hey I am happy if there are present to begin with! – Eugene May 08 '18 at 14:38
  • @Eugene We have about 5000 unit tests (methods) for several million lines of production code. The point is that we want to enable "full unit test runs" for our gerrit verification builds. So that you can't commit changes that break unit test. But then, we have to make sure that these verification builds complete within a reasonable amount of time. Of course, first of all, we have to gain experience. But it *might* be useful to allow for occasional hickups ... by enforcing such strict "run time" limitations. – GhostCat May 08 '18 at 16:16
  • @GhostCat - Sorry for the late response. I posted a reply nearly a year ago, and a moderator deleted my post. Hopefully someone will get value from my expanded re-post. – Scott Babcock Jan 08 '19 at 00:58
  • Does this answer your question? [JUnit 5 -- global timeout?](https://stackoverflow.com/questions/47041313/junit-5-global-timeout) – Lubo Jan 23 '20 at 19:40
  • Well, it answers for JUnit 5. But we are still using Junit 4. And JUnit 5 options were suggested and accepted in 2018 already. – GhostCat Jan 23 '20 at 19:49

4 Answers4

6

Although JUnit Jupiter (i.e., the programming and extension model introduced in JUnit 5) does not yet have built-in support for global timeouts, you can still implement global timeout support on your own.

The only catch is that a timeout extension cannot currently abort test execution preemptively. In other words, a timeout extension in JUnit Jupiter can currently only time the execution of tests and then throw an exception if the execution took too long (i.e., after waiting for the test to end, which may potentially never happen if the test hangs).

In any case, if you want to implement a non-preemptive global timeout extension for use with JUnit Jupiter, here's what you need to do.

  1. Look at the TimingExtension example in the JUnit 5 User Guide for inspiration. You'll need code similar to that, but you'll want to throw an exception if the duration exceeds a configured timeout. How you configure your global timeout is up to you: hard code it, look up the value from a JVM system property, look up the value from a custom annotation etc.
  2. Register your global timeout extension using Java's ServiceLoader mechanism. See Automatic Extension Registration for details.

Happy Testing!

Sam Brannen
  • 29,611
  • 5
  • 104
  • 136
  • But the TiminxExtension doesn't show us which callback to use, current ones are either before the execution or after, we need something that will trigger *during* the test execution so we can break it – Krzysztof Krasoń Dec 21 '18 at 16:35
  • Please re-read my answer. The `TimingExtension` _does_ demonstrate which extension APIs to implement in order to support **non-preemptive** timeouts. – Sam Brannen Dec 21 '18 at 16:45
  • As I said, "a timeout extension cannot currently abort test execution preemptively." – Sam Brannen Dec 21 '18 at 16:45
  • Yes, you are right I skipped to eagerly into the example without reading your full response. – Krzysztof Krasoń Dec 21 '18 at 17:53
  • 2
    This response doesn't meet the OP's requirements. Why is it tagged as the "accepted" answer? – Scott Babcock Jan 08 '19 at 02:02
  • Because the OP wrote this: https://stackoverflow.com/questions/50229133/how-to-enable-a-global-timeout-for-junit-testcase-runs/50233807?noredirect=1#comment87483800_50229133 – Sam Brannen Jan 08 '19 at 18:19
  • Ah, okay... For folks running with JUnit 4, I posted a response below that provides the global pre-emptive timeout behavior the OP was looking for. – Scott Babcock Jan 09 '19 at 00:09
4

Check out my JUnit 4 extension library (https://github.com/Nordstrom/JUnit-Foundation). Among the features provided by this library is the ability to define a global timeout value, which will be automatically applied to each test method that doesn't already define a longer timeout interval.

This library uses the Byte Buddy byte code generation library to install event hooks at strategic points in the test execution flow of JUnit 4. The global timeout is applied when JUnit has created a test class instance to run an "atomic" test.

To apply the global timeout, the library replaces the original @Test annotation with an object that implements the @Test interface. This approach utilizes all of JUnit's native timeout functionality, which provides pre-emptive termination of tests that run too long. The use of native timeout functionality eliminates the need for invasive implementation or special-case handling, and this functionality is activated without touching a single source file.

All of the updates needed to install and activate global timeout support are in the project file (POM / build.gradle) and optional properties file. The timeout interval can be overridden via System property, which enables adjustments to be made from the command line or programmatically. For scenarios where timeout failures are caused by transient conditions, you may want to pair the global timeout feature with the automatic retry feature.

Scott Babcock
  • 549
  • 4
  • 13
  • Haven't tried it out personally, but it sounds impressive. So I gave you a +1. ;-) – Sam Brannen Jan 09 '19 at 15:59
  • The use of a Java agent, however, does make it slightly less attractive, but I suppose there's no alternative given the architecture of JUnit 4. – Sam Brannen Jan 09 '19 at 16:00
  • @sam-brannen Thanks for the up-vote! Yes, byte code generation in a Java agent was the only way to meet my objectives in JUnit 4. I wouldn't recommend this approach if the platform was still in active development, given its reliance on undocumented behaviors and implementation-specific details that aren't defined by the public API. – Scott Babcock Jan 09 '19 at 22:24
  • @ScottBabcock It looks super interesting, do you have some example about how to configure `TEST_TIMEOUT` with a multi-module Gradle project? Thanks in advance! – Diego Gómez Olvera Oct 22 '21 at 09:26
  • @DiegoGómezOlvera I haven't worked on any multi-module Gradle projects. However, `TEST_TIMEOUT` can be specified through configuration files on the class path to have different values in different modules. You can also specify the value via Java system properties, which can be injected and inherited in a variety of ways. – Scott Babcock Mar 25 '22 at 05:09
2

What you're probably looking for is not implemented: https://github.com/junit-team/junit4/issues/140

Although, you can achieve the same results with simple inheritance.

Define an abstract parent class, like BaseIntegrationTest with the following @Rule field:

public abstract class BaseIntegrationTest extends RunListener {

    private static final int TEST_GLOBAL_TIMEOUT_VALUE = 10;

    @Rule
    protected Timeout globalTimeout = Timeout.seconds(TEST_GLOBAL_TIMEOUT_VALUE);

}

Then make it a parent for every test class within the scope. For example:

public class BaseEntityTest extends BaseIntegrationTest {

    @Before
    public void init() {
        // init
    }

    @Test
    public void twoPlusTwoTest() throws Exception {
        assert 2 + 2 == 4;        
    }
}

That's it.

Mikhail Kholodkov
  • 23,642
  • 17
  • 61
  • 78
  • Basically that is an "implementation" variation of what the other existing answer suggests to do. Upvoting for the effort, but not really "solving" the issue to introduce a global timeout without touching all test classes. And make no mistake: using inheritance is (somehow) worse than using composition ... – GhostCat May 08 '18 at 11:19
  • Thanks for the comment. Well, looks like this is the only possible solution I've found for now. Unless JUnit team will implement some bootstrap configurable variable in the future. – Mikhail Kholodkov May 08 '18 at 11:25
0

Currently, maybe you cannot because Junit 5 removed the Rule and replace with the Extension. The above example does not work because the example code implements the AfterTestExecutionCallback that will be invoked after the test method is done, so the timeout is not useful.

Hieu Pham
  • 23
  • 1
  • 8