-
Notifications
You must be signed in to change notification settings - Fork 7.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
2.x: Uncaught errors fail silently in junit tests. #5234
Comments
you may use TestObserver to subscribe the result |
Unlike 1.x, |
I did read that, but it doesn't really explain why it deals directly with the current thread's @0kai I'm not trying to figure out how to test errors, rather I'm trying to make sure that uncaught exceptions in tests aren't marked as passing. At least not silently anyway |
2.x
|
I think I'm not being clear. I'm not trying to test if |
Because you can't throw in a push/async environment and expect it to try-catch it. |
We have a very similar issue. The
If I understand this correctly, the direct Most likely this is not an issue for real-world environments, but this affects unit tests we run on JVM using JUnit. import org.junit.Test
import io.reactivex.Observable as RxJava2Observable
import rx.Observable as RxJava1Observable
class RxError {
@Test fun `RxJava 1 test failure with OnErrorNotImplementedException as expected`() {
RxJava1Observable.fromCallable { throw RuntimeException() }.subscribe()
}
@Test fun `RxJava 2 test success, just prints OnErrorNotImplementedException stacktrace not as expected`() {
RxJava2Observable.fromCallable { throw RuntimeException() }.subscribe()
}
} |
RxJava 2 only passes fatal exceptions upwards, which end up in the uncaught exception handler. Any other exception goes through
What stacktrace did you expect? |
Sorry for the poor wording. I meant that the expected behaviour from my side is to fail a test due to uncaught exception |
This is why RxJava's own test often utilize |
Yeah, this is very serious issue for us as well (sad that we started migration that late…) @akarnokd theoretically what if For RS subscriber everything will be ok, since Basically similar to how it works now with rule 3.9 #5274 and few other cases when if you use RxJava v2 specific types it can break specification, but if you use RS types — it follows RS. This is kinda breaking change, but not really, since in production ~no one wraps rx chains with try-catch "Because you can't throw in a push/async environment and expect it to try-catch it." and exceptions without try-catch will achieve thread uncaught handler normally as before while in tests users will actually see problems since test frameworks wrap tests with try-catch and reactive chains in tests are mostly synchronous. Plus we could attach RxJavaPlugin sample that imitates previous behavior to the changelog. |
The |
Right, but users will have to put "In production" you typically have an entrypoint (Application classes exist in most of both in front- and backend frameworks) which is a convenient place to hook into RxJavaPlugins and set desired behavior. While tests are usually isolated from each other and each class if not method will require customization for um, detecting errors… which is basically what each test should do by default… It just seems wrong to not inverse current behavior. @akarnokd what are your concerns about throwing Mine is that it can create sort of unexpected behavior if you'll try to mix RxJava v2 with some other RS implementations in compare to how RxJava v2 will work in isolation, but I'm not sure that mixing is common use case. I'm much more afraid that current behavior is very unexpected for v1 users who are migrating to v2. |
Throwing anything but fatal exceptions is undefined behavior. I guess you either manually write those unit tests or have some sort of generator. If written manually, you can always compose with extra checks before subscribing to a source. If generated, I assume the generator is written manually since I don't think any testing framework has first class support for RxJava yet. Add the plugin error tracking via public static <T> TestObserver<T> createTester(Observable<T> source, Observer<T> mocked) {
TestObserver<T> to = mocked != null ? new TestObserver<>(mocked) : new TestObserver<>();
List<Throwable> list = Collections.synchronizedList(new ArrayList<>());
return source.doOnSubscribe(s -> {
RxJavaPlugins.reset();
RxJavaPlugins.setErrorHandler(e -> list.add(e));
})
.doFinally(() -> {
RxJavaPlugins.reset();
for (Throwable e : list) {
to.onError(e);
}
})
.subscribeWith(to);
} |
In RxJava v1 |
It wasn't in the original set of fatal exceptions of 2.x and throwing from Reactive-Streams is somewhat of a gamble. For example, a multi-library flow may not consider it as fatal or swallow it completely anyway (but most will consider an OutOfMemoryError as such btw). The most reliable way for exceptions is down where I believe RxJava already offers the necessary hooking capabilities to detect such errors in tests; most likely you have to mock out schedulers already, making the test synchronous and at the end, you can synchronously check any excess errors with an approach like I showed above. |
That's what I want to avoid and throw only if RxJava v2 specific subscriber was used.
As said before, v2 violates some RS rules if v2 specific subscriber was used, but respects RS if RS subscriber comes. And I can create problematic multi-library flow because of that, but only if I'd like to.
Btw, this is already happening with RxJava v1 and v2 connected through interop library, v2 swallows @Test
fun interopProblem() {
try {
Observable2
.fromCallable { throw RuntimeException("Test") }
.toObservable1()
.subscribe()
fail("Should throw rx.exceptions.OnErrorNotImplementedException")
} catch (expected: rx.exceptions.OnErrorNotImplementedException) {
// ok.
}
}
Good point, but. Scheduling is explicit, our code will not compile if we won't pass scheduler (either real or test one). While current design of error handling is implicit and not only allows tests to compile without checks suggested above, but also returns normally and tests pass as false positives. |
There is a lint check for such use cases: https://bitbucket.org/littlerobots/rxlint |
We normally don't have errors in streams and convert them to values using Kotlin sealed classes, so we don't use But we need to detect unexpected errors in tests.
And we have 3k+ test cases written with different test frameworks, hooking into each test class is manual and error-prone work. There are hundreds if not thousands other projects migrating from v1 to v2 that'll have to do the same and this number will only grow. |
Write a custom |
Please correct me if I'm wrong but:
Which makes possible to do this change in 2.x and allow users test Rx code as before with 1.x. |
@hzsweers Just curious — how did you solve this issue on your side? |
We just have a junit rule that installs an error hook via rxjava plugins that throws it |
This is actually a problem outside of tests too unfortunately. Say I'm on Android and I want to write a custom plugin for decorating observers (such as tracking analytics). I want to give the delegate observer a shot at Installing a custom error handler via plugins is not an option either, as it is just try/catch'd and pipes the error again through the above Could I propose making this configurable via system property? Something like Edit: Proposed a strawman impl in #5569 |
This is a strawman example/proposal for configurable uncaught exception handling in RxJavaPlugins. This is in ref to ReactiveX#5234. The two options are to defer to current thread's handler (the current implementation and remains the default) and an optional `throw` option that wraps the throwable into an `UncaughtRxJavaException` for developers to watch for separately (and rethrowing, which requires some sort of `RuntimeException` wrapping).
I'd say this use case has now a workaround with #5590 so closing it. |
Would be great to have a snippet for such a workaround if any. |
^ for tests anyway. For prod, you can basically install an observer subscribe hook and watch for those interfaces |
@hzsweers thanks! Still feels like a bit of complex solution and hardly portable to e.g. Spek :( |
Thanks for the Rule, @hzsweers. Not super cool to use something like this, but no choice. It's something. |
Permalink to the current version of |
I'd like to re-kick the proposal for a property or hook to configure this. While the workarounds of the errors rule for tests and Say you have a delegating observer, such as what we have in AutoDispose. In an environment where the uncaught exception handler just calls In #5569 part of the discussion proposals was something like a |
Okay, let's see the proposed code changes. |
PR opened: #6378 |
https://github.com/vRallev just solved this for us in a pretty clean way at Square: introduced a simple jvmAgent that sets up a default uncaught exception handler, and some gradle tweaking to make sure that our unit tests all depend on the module that puts the agent into effect. I plan to steal the technique for https://github.com/square/workflow, will try to remember to ping here when I do so. |
We're using that java agent technique but JVM tests still seem to pass in some cases. At least we get this in test output:
|
I was writing a utility that involved decorating observers and catching uncaught errors from the delegate observer's
onError
handling when I noticed in testing that tests always passed. There would be log output, but tests would pass none the less.Demo of the issue can be found here, but the gist of it is that the following test passes in standard JUnit:
I'm trying to understand why errors are passed directly to the handler rather than just thrown, and also curious to get thoughts on making this behavior configurable (if for no other reason than to disable in junit tests). I can't find any details about why this behavior happens in JUnit either.
I think this has worrisome implications. In our codebase alone, I dropped in an error watching rule to track these and discovered over 150 tests with uncaught exceptions that are being marked as passing otherwise.
The text was updated successfully, but these errors were encountered: