To whom it may concern,
I'd like to be able to have a test, which fails if a particular code snippet takes longer than a certain amount of time to execute.
As far as I can tell, there is currently only the ability to set a timeout at the test level... however I feel this has the undesirable side effect of validating the execution time of the test code... I'd like to test only the execution code.
More than happy to complete a pull request - depending on whether this behaviour is already supported and whether lambdas are supported.
An example:
Given the following timeout method
/**
* Ensures a function completes within the given timeout.
*/
public static <Result> Result timeout(final long timeoutMillis, Supplier<Result> invoke){
long start = System.currentTimeMillis();
Result result = invoke.get();
long duration = System.currentTimeMillis() - start;
if (duration > timeoutMillis) {
Assert.fail(String.format("Actual time %s exceeded timeout %s (millis).", duration,
timeoutMillis));
}
return result;
}
And the sayHello method takes 1000 milliseconds to return
When I test the sayHello method
@Test
public void testSomeMethod(){
String greeting = timeout(500, () -> greetingService.sayHello("Nick"));
assertEquals("Hello Nick", greeting);
}
Then the test should fail with the message Actual time 1000 exceeded timeout 500 (millis).
Hi @nickgrealy, please correct me if I've misunderstood what you're requesting, but I believe this has actually been implemented already in JUnit 5 via Assertions.assertTimeout and Assertions.assertTimeoutPreemptively.
I'm wondering if your confusion is coming from the fact that the documentation you linked applies to JUnit 4 rather than JUnit 5? :smiley:
Hi @jbduncan, no I hadn't seen that (-> assertTimeout)... looks good!
I'll just preface the next statement with: I haven't used the functional interface Executable yet...
Can we improve the existing code, by adding additional method signature(s), to use a Supplier<Result> and return the supplied object? That way we can:
someClass::someMethod)(N.B. I'm not sure how Java will handle the similar functional interface signatures, if they clash perhaps we could put them in separate classes?)
e.g.
static <Result> Result assertTimeout(Duration timeout, Supplier<Result> executable) { ... }
Hi guys,
I was gonna say that I already implemented assertions for timeouts in JUnit Jupiter, but @jbduncan beat me to it. 馃槈
@nickgrealy, regarding support for Supplier<T> in addition to Executable, that's definitely worth considering.
Though we would likely implement the alternative to the existing assertTimeout() as follows.
static <T> T assertTimeoutWithResult(Duration timeout, Supplier<T> supplier) { ... }
Similarly, the remaining method would likely be something like assertTimeoutWithResultPreemptively().
I will therefore rename this issue's title accordingly
Regards,
Sam
@junit-team/junit-lambda, let's discuss how to name such methods.
@sbrannen - awesome!
There are only two hard things in Computer Science: cache invalidation and naming things (, and off-by-one errors).
Let me know if I can help implement the solution. I'm sure it's trivial - but I'd love to have contributed to Junit5!
Ahhhh, yes... one of my favorite CS jokes. 馃槈
Let me know if I can help implement the solution. I'm sure it's trivial - but I'd love to have contributed to Junit5!
You're right: it is rather trivial, and normally we just implement stuff like that ourselves in order to save time. However, since you feel so inclined, sure.... give it a shot. Please just use the existing code to guide you in your solution and documentation. Also, make sure to write appropriate unit tests and to update the release notes for M4 accordingly.
Have fun!
Assigned to @nickgrealy and tagged as _in progress_.
Please note that Supplier is insufficient for our purposes.
We will therefore need a ThrowingSupplier analogous to our existing ThrowingConsumer which resides in the org.junit.jupiter.api.function package.
@junit-team/junit-lambda, let's discuss how to name such methods.
Shouldn't we first decide on a name?
I think overloading the methods would be fine, no need for a WithResult suffix.
Other opinions?
Shouldn't we first decide on a name?
Well.. it wouldn't hurt. 馃槆
All kidding aside: renaming the methods ex post facto wouldn't be that bad, but deciding up front is likely better.
I think overloading the methods would be fine, no need for a
WithResultsuffix.
True. I actually just implemented it as a proof of concept, and overloading works totally fine. 馃憤
@Test
void test() {
String foo = assertTimeout(ofMillis(50), () -> {
// do something that takes a bit of time...
return "foo";
}, () -> "Tempus" + " " + "Fugit");
assertEquals("foo", foo);
}
So, this is the signature/implementation of one of the simplistic timeouts in Assertions:
public static <T> T assertTimeout(Duration timeout, ThrowingSupplier<T> supplier, Supplier<String> messageSupplier) {
return AssertTimeout.assertTimeout(timeout, supplier, messageSupplier);
}
And I'll leave the rest of the exercise to @nickgrealy.
@sbrannen - quick question: I'm just implementing the assertTimeoutPreemptively method, and I want to capture the result OR the caught exception... the existing Future was only expecting a Throwable. i.e.
Future<Throwable> future = executorService.submit(() -> {
try {
return supplier.get();
} catch (Throwable ex) {
return ex;
}
return null;
});
How do you want me to tackle this one?
Optional<T> result and Optional<Throwable> exception, and have Future<ResultOrException> future = ... (which package and what name would you want?)Hi @sbrannen,
I've pushed some changes into my own fork/branch for now, can you please review them before I submit a pull request?
Of particular note:
AssertionsDemo.java - converted tabs from tab character to 4 spaces (was this the spotlessApply task & should I revert?)Assertions.java - Added <P> tags (was this the spotlessApply task & should I revert?)AssertionsAssertTimeoutTests.java - would you like these tests re-written as a Parameterized test?AssertTimeout.java - The ResultOrException inner class - are you happy with this solution?Thanks,
Nick
Hi @nickgrealy,
I unfortunately don't have any spare time at the moment.
@junit-team/junit-lambda, maybe one of you can provide some guidance here?
AssertionsDemo.java- converted tabs from tab character to 4 spaces (was this thespotlessApplytask & should I revert?)
No, that was most probably your IDE. This file is mostly ignored by Spotless because there's a // @formatter:off immediately after the package statement. Can you please revert the whitespace changes?
Assertions.java- Added <P> tags (was this thespotlessApplytask & should I revert?)
Nope, that wasn't Spotless either. In fact the build now produces a large number of warnings, e.g.
:junit-jupiter-api:javadoc
/Users/marcp/Repositories/junit5/junit-jupiter-api/src/main/java/org/junit/jupiter/api/function/ThrowingSupplier.java:21: warning: empty <p> tag
* <p>
^
[...]
/Users/marcp/Repositories/junit5/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Assertions.java:32: warning: empty <p> tag
* <p>
^
31 warnings
AssertionsAssertTimeoutTests.java- would you like these tests re-written as a Parameterized test?
I think we can live with the duplication for now. Especially since we don't have parameterized test support in Jupiter, yet.
AssertTimeout.java- TheResultOrExceptioninner class - are you happy with this solution?
I think this version is easier and does not need ResultOrException:
static <T> T assertTimeoutPreemptively(Duration timeout, ThrowingSupplier<T> supplier,
Supplier<String> messageSupplier) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
Future<T> future = executorService.submit(() -> {
try {
return supplier.get();
} catch (Throwable throwable) {
throw ExceptionUtils.throwAsUncheckedException(throwable);
}
});
long timeoutInMillis = timeout.toMillis();
try {
return future.get(timeoutInMillis, TimeUnit.MILLISECONDS);
}
catch (TimeoutException ex) {
throw new AssertionFailedError(
buildPrefix(nullSafeGet(messageSupplier)) + "execution timed out after " + timeoutInMillis + " ms");
}
catch (ExecutionException ex) {
throw ExceptionUtils.throwAsUncheckedException(ex.getCause());
}
catch (Throwable ex) {
throw ExceptionUtils.throwAsUncheckedException(ex);
}
}
finally {
executorService.shutdownNow();
}
}
@nickgrealy Do you have time to make these changes?
@marcphilipp - thanks for the feedback. I sure do, will make the changes today.
Hi @jbduncan, @marcphilipp, @sbrannen,
I've made the changes as suggested (I've also reverted the import re-ordering on AssertionsDemo.java)... is there anything else you'd like changed?
JUnit Contributor License Agreement?Hi @nickgrealy.
Thank you for taking the time to do this. I'm impressed by the cleanliness of your code, so well done!
Please have a look at https://github.com/nickgrealy/junit5/commit/5d9526ae4d1a74d57fa30a8b3bed9b09de78050c#commitcomment-20774063, where I made some final review comments on both minor nitpicks and small javadoc issues I found in the code.
If you were to fix those, then that would completely satisfy me!
As for squashing commits & raising a PR, I'm sure the JUnit team would be able to guide you on that.
With the JUnit Contributor License Agreement, as long as you agree with the terms in CONTRIBUTING.md and put the following text at the bottom of your PR body, then that should be all you need to do to "sign" it. :)
I hereby agree to the terms of the JUnit Contributor License Agreement.
@jbduncan - thanks for the feedback! Changes have been made.
Thanks, looks good to me now. :)
Oh very sorry @nickgrealy, I just noticed two other areas for improvement which I missed the first time round.
{@code [...]} brackets, for consistency with {@code supplier} blocks in the assertTimeout*(*) methods you introduced?executable-based assertTimeout methods.)@see org.junit.jupiter.api.Assertions#assertTimeout(Duration, ThrowingSupplier) under this line in ThrowingSupplier, so that people know a main place where ThrowingSuppliers are used?@jbduncan - thanks, changes have been made.
Thank you very much for bearing with me @nickgrealy - now your changes look _very_ good to me. :)
@nickgrealy Don't worry about squashing. We can do that when we merge. Please go ahead and submit a pull request. Thanks!
All (@jbduncan, @marcphilipp, @sbrannen), thanks for that. It was far less painful then I anticipated!
Most helpful comment
No, that was most probably your IDE. This file is mostly ignored by Spotless because there's a
// @formatter:offimmediately after the package statement. Can you please revert the whitespace changes?Nope, that wasn't Spotless either. In fact the build now produces a large number of warnings, e.g.
I think we can live with the duplication for now. Especially since we don't have parameterized test support in Jupiter, yet.
I think this version is easier and does not need
ResultOrException:@nickgrealy Do you have time to make these changes?