, LambdaConsumerIntrospection {
29 |
30 | @Override
31 | public void onSubscribe(Subscription s) {
32 | s.request(Long.MAX_VALUE);
33 | }
34 |
35 | @Override
36 | public void onNext(T t) {}
37 |
38 | @Override
39 | public final void onError(Throwable e) {}
40 |
41 | @Override
42 | public void onComplete() {}
43 |
44 | @Override
45 | public final boolean hasCustomOnError() {
46 | return false;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/RxDogTagTaggedExceptionReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
19 |
20 | /**
21 | * A marker type to indicate that RxDogTag's should pass the decorated stacktrace to {@link
22 | * #onError(Throwable)}. Note that this will always be tried directly, and no guarded delegate
23 | * behavior will be attempted even if {@link RxDogTag.Builder#guardObserverCallbacks(boolean)} is
24 | * enabled in configuration.
25 | *
26 | * NOTE: RxDogTag exceptions are always {@link OnErrorNotImplementedException
27 | * OnErrorNotImplementedExceptions}, as these have special behavior as an escape hatch in RxJava
28 | * internals. This exception will have no "cause" property if the original exception was not already
29 | * an {@link OnErrorNotImplementedException}. If it was, which is unusual, it is reused with its
30 | * original cause (if any) and has its stacktrace modified.
31 | */
32 | public interface RxDogTagTaggedExceptionReceiver extends RxDogTagErrorReceiver {}
33 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | pluginManagement {
18 | apply from: file('gradle/dependencies.gradle')
19 | resolutionStrategy {
20 | eachPlugin {
21 | switch (requested.id.id) {
22 | case 'me.champeau.gradle.jmh':
23 | useVersion(deps.versions.jmhPlugin)
24 | break
25 | case 'org.jetbrains.dokka':
26 | useModule("org.jetbrains.dokka:dokka-gradle-plugin:${deps.versions.dokka}")
27 | break
28 | case 'net.ltgt.errorprone':
29 | useVersion(deps.versions.errorPronePlugin)
30 | break
31 | case 'net.ltgt.nullaway':
32 | useVersion(deps.versions.nullawayPlugin)
33 | break
34 | case 'ru.vyarus.animalsniffer':
35 | useVersion(deps.versions.animalSniffer)
36 | break
37 | }
38 | }
39 | }
40 | repositories {
41 | mavenCentral()
42 | gradlePluginPortal()
43 | jcenter()
44 | }
45 | }
46 |
47 | rootProject.name = 'rxdogtag-root'
48 | if (System.getenv("ANDROID_HOME") != null) {
49 | include ':android-benchmark:benchmark'
50 | }
51 | include ':rxdogtag'
52 | include ':rxdogtag-autodispose'
53 |
--------------------------------------------------------------------------------
/rxdogtag-autodispose/src/main/java/rxdogtag2/autodispose2/AutoDisposeConfigurer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2.autodispose2;
17 |
18 | import autodispose2.AutoDispose;
19 | import java.util.Collections;
20 | import java.util.Set;
21 | import rxdogtag2.ObserverHandler;
22 | import rxdogtag2.RxDogTag;
23 |
24 | /**
25 | * Configures an {@link RxDogTag.Builder} with:
26 | *
27 | *
28 | * A {@link ObserverHandler} that supports handling AutoDispose's decorating observers to
29 | * retrieve their underlying delegate observers.
30 | * Ignored packages.
31 | *
32 | *
33 | * Usage: Configure with {@link #configure(RxDogTag.Builder)}.
34 | */
35 | public final class AutoDisposeConfigurer {
36 |
37 | private AutoDisposeConfigurer() {}
38 |
39 | private static final Set IGNORE_PACKAGES =
40 | Collections.singleton(
41 | // "com.uber.autodispose"
42 | AutoDispose.class.getPackage().getName());
43 |
44 | public static void configure(RxDogTag.Builder builder) {
45 | builder
46 | .addObserverHandlers(AutoDisposeObserverHandler.INSTANCE)
47 | .addIgnoredPackages(IGNORE_PACKAGES);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/rxdogtag/src/jmh/java/rxdogtag2/EventHandlingPerf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import io.reactivex.rxjava3.core.Observer;
19 | import io.reactivex.rxjava3.observers.TestObserver;
20 | import org.openjdk.jmh.annotations.*;
21 | import org.openjdk.jmh.infra.Blackhole;
22 | import rxdogtag2.util.PerfObserver;
23 |
24 | /**
25 | * Measures the time that RxDogTag's event handling takes.
26 | *
27 | * We only measure `Observer` here because the handling is currently identical across all
28 | * consumers.
29 | */
30 | @BenchmarkMode(Mode.Throughput)
31 | @State(Scope.Thread)
32 | public class EventHandlingPerf {
33 |
34 | @Param({"false", "true"})
35 | public boolean useRxDogTag;
36 |
37 | Observer observer = new TestObserver();
38 |
39 | @Setup
40 | public void setup(final Blackhole bh) {
41 | Observer bhObserver = new PerfObserver(bh);
42 |
43 | if (useRxDogTag) {
44 | RxDogTag.Configuration configuration = new RxDogTag.Configuration(new RxDogTag.Builder());
45 | observer = new DogTagObserver(configuration, bhObserver);
46 | } else {
47 | observer = bhObserver;
48 | }
49 | }
50 |
51 | @Benchmark
52 | public void onNext(Blackhole bh) {
53 | observer.onNext(12345);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ###OSX###
2 |
3 | .DS_Store
4 | .AppleDouble
5 | .LSOverride
6 |
7 | # Icon must ends with two \r.
8 | Icon
9 |
10 |
11 | # Thumbnails
12 | ._*
13 |
14 | # Files that might appear on external disk
15 | .Spotlight-V100
16 | .Trashes
17 |
18 |
19 | ###Linux###
20 |
21 | *~
22 |
23 | # KDE directory preferences
24 | .directory
25 |
26 |
27 | ###Android###
28 |
29 | # Built application files
30 | *.apk
31 | *.ap_
32 |
33 | # Files for ART and Dalvik VM
34 | *.dex
35 |
36 | # Java class files
37 | *.class
38 |
39 | # Generated files
40 | bin/
41 | gen/
42 | out/
43 |
44 | # Gradle files
45 | .gradle/
46 | .gradletasknamecache
47 | build/
48 |
49 | # Local configuration file (sdk path, etc)
50 | local.properties
51 |
52 | # Lint
53 | lint-report.html
54 | lint-report_files/
55 | lint_result.txt
56 |
57 | # Mobile Tools for Java (J2ME)
58 | .mtj.tmp/
59 |
60 | # Package Files #
61 | *.war
62 | *.ear
63 |
64 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
65 | hs_err_pid*
66 |
67 |
68 | ###IntelliJ###
69 |
70 | *.iml
71 | *.ipr
72 | *.iws
73 | .idea/
74 |
75 |
76 | ###Eclipse###
77 |
78 | *.pydevproject
79 | .metadata
80 | tmp/
81 | *.tmp
82 | *.bak
83 | *.swp
84 | *~.nib
85 | .settings/
86 | .loadpath
87 |
88 | # External tool builders
89 | .externalToolBuilders/
90 |
91 | # Locally stored "Eclipse launch configurations"
92 | *.launch
93 |
94 | # CDT-specific
95 | .cproject
96 |
97 | # PDT-specific
98 | .buildpath
99 |
100 | # sbteclipse plugin
101 | .target
102 |
103 | # TeXlipse plugin
104 | .texlipse
105 |
106 | # kotlin
107 | annotations/
108 |
109 | # Misc
110 | .vscode/
111 | .project/
112 |
113 | # Website
114 | docs/0.x/*
115 | docs/1.x/*
116 | docs/2.x/*
117 | docs/index.md
118 | docs/changelog.md
119 | docs/contributing.md
120 | docs/code-of-conduct.md
121 |
--------------------------------------------------------------------------------
/gradle/dependencies.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | def versions = [
18 | animalSniffer: '1.5.0',
19 | androidBenchmark: '1.0.0',
20 | errorProne: '2.3.3',
21 | errorPronePlugin: '0.7.1',
22 | gjf: '1.7',
23 | jmhLib: '1.21',
24 | jmhPlugin: '0.4.8',
25 | nullawayPlugin: '0.1',
26 | dokka: '0.10.1',
27 | spotless: '3.27.2'
28 | ]
29 |
30 | def build = [
31 | ci: 'true' == System.getenv('CI'),
32 | javaVersion: JavaVersion.VERSION_1_8,
33 |
34 | errorProne: "com.google.errorprone:error_prone_core:${versions.errorProne}",
35 | errorProneJavac: "com.google.errorprone:javac:9+181-r4173-1",
36 | errorProneCheckApi: "com.google.errorprone:error_prone_check_api:${versions.errorProne}",
37 | errorProneTestHelpers: "com.google.errorprone:error_prone_test_helpers:${versions.errorProne}",
38 | nullAway: 'com.uber.nullaway:nullaway:0.7.9',
39 | animalSniffer: 'org.codehaus.mojo.signature:java17:1.0@signature'
40 | ]
41 |
42 | def rx = [
43 | autodispose: 'com.uber.autodispose2:autodispose:2.0.0',
44 | java: 'io.reactivex.rxjava3:rxjava:3.0.0'
45 | ]
46 |
47 | def test = [
48 | junit: 'junit:junit:4.12',
49 | truth: 'com.google.truth:truth:1.0.1'
50 | ]
51 |
52 | ext.deps = [
53 | "build": build,
54 | "rx": rx,
55 | "test": test,
56 | "versions": versions
57 | ]
58 |
--------------------------------------------------------------------------------
/rxdogtag/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | id 'ru.vyarus.animalsniffer'
19 | id 'me.champeau.gradle.jmh'
20 | id 'org.jetbrains.dokka'
21 | }
22 |
23 | dependencies {
24 | api deps.rx.java
25 |
26 | signature deps.build.animalSniffer
27 | testImplementation deps.test.junit
28 | testImplementation deps.test.truth
29 | }
30 |
31 | dokka {
32 | // Output as Github flavored markdown so that docs show up inline in mkdocs website
33 | outputFormat = 'gfm'
34 | outputDirectory = "$rootDir/docs/2.x"
35 |
36 | configuration {
37 | platform = "JVM"
38 | classpath = [project.tasks['assemble'].outputs.files.files]
39 | sourceRoot {
40 | path = "src/main/java"
41 | }
42 | externalDocumentationLink {
43 | url = new URL("http://reactivex.io/RxJava/3.x/javadoc/")
44 | }
45 | }
46 | }
47 |
48 | // We setup jmh such that you must provide the `-Pjmh` to specify which tests
49 | // to run. If it has no value then all tests are run, but you can specify
50 | // tests by providing a value. (e.g., if you want to run "SubscriptionPerf",
51 | // you can run `./gradlew jmh -Pjmh=SubscriptionPerf`)
52 | jmh {
53 | jmhVersion = deps.versions.jmhLib
54 |
55 | if (project.hasProperty("jmh")) {
56 | include = [".*" + project.jmh + ".*"]
57 | println("JMH: " + include)
58 | }
59 | }
60 |
61 | apply plugin: 'com.vanniktech.maven.publish'
62 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to RxDogTag
2 | ========================
3 |
4 | The Uber team welcomes contributions of all kinds, from simple bug reports through documentation, test cases,
5 | bugfixes, and features.
6 |
7 | Workflow
8 | --------
9 |
10 | We love GitHub issues!
11 |
12 | For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice.
13 |
14 | For big features, please open an issue so that we can agree on the direction, and hopefully avoid
15 | investing a lot of time on a feature that might need reworking.
16 |
17 | Small pull requests for things like typos, bugfixes, etc are always welcome.
18 |
19 | ### Code style
20 |
21 | This project uses [ktlint](https://github.com/pinterest/ktlint) and [GJF](https://github.com/google/google-java-format),
22 | provided via the [spotless](https://github.com/diffplug/spotless) gradle plugin.
23 |
24 | If you find that one of your pull reviews does not pass the CI server check due to a code style
25 | conflict, you can easily fix it by running: ./gradlew spotlessApply.
26 |
27 | Generally speaking - we use vanilla ktlint + 2space indents, and vanilla GJF. You can integrate both of
28 | these in IntelliJ code style via either [GJF's official plugin](https://plugins.jetbrains.com/plugin/8527-google-java-format)
29 | or applying code style from Jetbrains' official style.
30 |
31 | No star imports please!
32 |
33 | DOs and DON'Ts
34 | --------------
35 |
36 | * DO follow our coding style
37 | * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken.
38 | * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion.
39 | * DO run all Gradle verification tasks (`./gradlew check`) before submitting a pull request
40 |
41 | * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it.
42 |
--------------------------------------------------------------------------------
/android-benchmark/benchmark/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019 Uber Technologies, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: "com.android.library"
18 | apply plugin: "org.jetbrains.kotlin.android"
19 | apply plugin: "androidx.benchmark"
20 |
21 | android {
22 | compileSdkVersion 29
23 |
24 | defaultConfig {
25 | minSdkVersion 14
26 | targetSdkVersion 29
27 | }
28 | compileOptions {
29 | sourceCompatibility = JavaVersion.VERSION_1_8
30 | targetCompatibility = JavaVersion.VERSION_1_8
31 | }
32 | sourceSets {
33 | findByName("androidTest")?.java?.srcDirs("src/androidTest/kotlin")
34 | findByName("main")?.java?.srcDirs("src/main/kotlin")
35 | }
36 | }
37 |
38 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
39 | kotlinOptions {
40 | jvmTarget = "1.8"
41 | }
42 | }
43 |
44 | dependencies {
45 | implementation "androidx.benchmark:benchmark-common:${deps.versions.androidBenchmark}"
46 | implementation "androidx.benchmark:benchmark-junit4:${deps.versions.androidBenchmark}"
47 | implementation "androidx.test:runner:1.2.0"
48 | implementation "androidx.test:rules:1.2.0"
49 | implementation "androidx.test.ext:junit:1.1.1"
50 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.3.50"
51 | implementation "io.reactivex.rxjava3:rxandroid:3.0.0"
52 | implementation deps.test.junit
53 | implementation project(":rxdogtag")
54 | androidTestImplementation configurations.getByName("implementation")
55 | }
56 |
--------------------------------------------------------------------------------
/rxdogtag/src/jmh/java/rxdogtag2/SubscriptionPerf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import io.reactivex.rxjava3.core.Observable;
19 | import io.reactivex.rxjava3.disposables.Disposable;
20 | import io.reactivex.rxjava3.functions.Consumer;
21 | import io.reactivex.rxjava3.internal.functions.Functions;
22 | import io.reactivex.rxjava3.plugins.RxJavaPlugins;
23 | import org.openjdk.jmh.annotations.*;
24 |
25 | /** Measures the time it takes to simply subscribe (but not consume any events). */
26 | @BenchmarkMode(Mode.Throughput)
27 | @State(Scope.Thread)
28 | public class SubscriptionPerf {
29 |
30 | @Param({"false", "true"})
31 | public boolean installRxDogTag;
32 |
33 | // Setup before test to narrow in on what we want to benchmark
34 | Observable emptyObservable = Observable.empty();
35 | Consumer emptyStringConsumer = Functions.emptyConsumer();
36 | Consumer emptyThrowableConsumer = Functions.emptyConsumer();
37 |
38 | @Setup
39 | public void setup() {
40 | if (installRxDogTag) {
41 | RxDogTag.install();
42 | }
43 | }
44 |
45 | @TearDown
46 | public void tearDown() {
47 | RxJavaPlugins.reset();
48 | }
49 |
50 | @Benchmark
51 | public Disposable subscribeWithoutOnError() {
52 | return emptyObservable.subscribe(emptyStringConsumer);
53 | }
54 |
55 | @Benchmark
56 | public Disposable subscribeWithOnError() {
57 | return emptyObservable.subscribe(emptyStringConsumer, emptyThrowableConsumer);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/DogTagTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static com.google.common.truth.Truth.assertThat;
19 |
20 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
21 | import java.util.Locale;
22 | import rxdogtag2.RxDogTag;
23 |
24 | interface DogTagTest {
25 |
26 | /** This basically tests {@link RxDogTag#guardedDelegateCall}. See its doc for more details. */
27 | default void assertUnwrappedError(
28 | Throwable e,
29 | int expectedLineNumber,
30 | OnErrorNotImplementedException originalError,
31 | String delegateType) {
32 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
33 | assertThat(e).hasMessageThat().isEqualTo(originalError.getCause().getMessage());
34 | assertThat(e.getStackTrace()).isEmpty();
35 | Throwable cause = e.getCause();
36 | assertThat(cause).isNotNull();
37 | assertThat(cause.getStackTrace()[0].getFileName())
38 | .isEqualTo(getClass().getSimpleName() + ".java");
39 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(expectedLineNumber);
40 | assertThat(cause.getStackTrace()[1].getClassName())
41 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
42 | assertThat(cause.getStackTrace()[2].getClassName())
43 | .isEqualTo(String.format(Locale.US, RxDogTag.STACK_ELEMENT_SOURCE_DELEGATE, delegateType));
44 | assertThat(cause.getStackTrace()[3].getClassName())
45 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/rxdogtag/src/jmh/java/rxdogtag2/EntireProcessPerf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import io.reactivex.rxjava3.core.Flowable;
19 | import io.reactivex.rxjava3.core.Observable;
20 | import io.reactivex.rxjava3.disposables.Disposable;
21 | import io.reactivex.rxjava3.plugins.RxJavaPlugins;
22 | import java.util.Arrays;
23 | import org.openjdk.jmh.annotations.*;
24 | import org.openjdk.jmh.infra.Blackhole;
25 | import rxdogtag2.util.PerfConsumer;
26 |
27 | /** Measures the time it takes to both subscribe and consume events. */
28 | @BenchmarkMode(Mode.Throughput)
29 | @State(Scope.Thread)
30 | public class EntireProcessPerf {
31 |
32 | @Param({"1", "1000", "1000000"})
33 | public int times;
34 |
35 | @Param({"false", "true"})
36 | public boolean installRxDogTag;
37 |
38 | Flowable flowable = Flowable.empty();
39 | Observable observable = Observable.empty();
40 |
41 | @Setup
42 | public void setup(Blackhole bh) {
43 | if (installRxDogTag) {
44 | RxDogTag.install();
45 | }
46 |
47 | Integer[] array = new Integer[times];
48 | Arrays.fill(array, 777);
49 |
50 | flowable = Flowable.fromArray(array);
51 |
52 | observable = Observable.fromArray(array);
53 | }
54 |
55 | @TearDown
56 | public void tearDown() {
57 | RxJavaPlugins.reset();
58 | }
59 |
60 | @Benchmark
61 | public Disposable flowable(Blackhole bh) {
62 | return flowable.subscribe(new PerfConsumer(bh));
63 | }
64 |
65 | @Benchmark
66 | public Disposable observable(Blackhole bh) {
67 | return observable.subscribe(new PerfConsumer(bh));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/ReadMeExample.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import io.reactivex.rxjava3.core.Observable;
19 | import io.reactivex.rxjava3.schedulers.Schedulers;
20 | import java.util.concurrent.CountDownLatch;
21 | import java.util.concurrent.TimeUnit;
22 | import org.junit.After;
23 | import org.junit.Ignore;
24 | import org.junit.Rule;
25 | import org.junit.Test;
26 | import rxdogtag2.RxDogTag;
27 |
28 | /** This is the code for the README examples. Un-ignore to test yourself. */
29 | @Ignore
30 | @SuppressWarnings("CheckReturnValue")
31 | public class ReadMeExample {
32 |
33 | @Rule
34 | public AggressiveUncaughtExceptionHandlerRule handlerRule =
35 | new AggressiveUncaughtExceptionHandlerRule();
36 |
37 | @After
38 | public void tearDown() {
39 | RxDogTag.reset();
40 | }
41 |
42 | @Test
43 | public void simpleSubscribe() {
44 | RxDogTag.install();
45 | Observable.error(new RuntimeException("Unhandled error!")).subscribe();
46 | }
47 |
48 | @Test
49 | public void complex() throws InterruptedException {
50 | RxDogTag.install();
51 | CountDownLatch latch = new CountDownLatch(1);
52 | Observable.just(1).subscribeOn(Schedulers.io()).map(i -> null).subscribe();
53 | latch.await(1, TimeUnit.SECONDS);
54 | }
55 |
56 | @Test
57 | public void complexDelegate() throws InterruptedException {
58 | RxDogTag.install();
59 | CountDownLatch latch = new CountDownLatch(1);
60 | Observable.just(1).subscribeOn(Schedulers.io()).subscribe(i -> throwSomething());
61 | latch.await(1, TimeUnit.SECONDS);
62 | }
63 |
64 | private void throwSomething() {
65 | throw new RuntimeException("Unhandled error!");
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/RxErrorsRule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static com.google.common.truth.Truth.assertThat;
19 |
20 | import io.reactivex.rxjava3.exceptions.CompositeException;
21 | import io.reactivex.rxjava3.exceptions.UndeliverableException;
22 | import io.reactivex.rxjava3.plugins.RxJavaPlugins;
23 | import java.util.ArrayList;
24 | import java.util.List;
25 | import java.util.NoSuchElementException;
26 | import java.util.concurrent.BlockingDeque;
27 | import java.util.concurrent.LinkedBlockingDeque;
28 | import java.util.concurrent.TimeUnit;
29 | import org.junit.rules.TestWatcher;
30 | import org.junit.runner.Description;
31 |
32 | /** JUnit rule to record RxJava errors. */
33 | @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
34 | public class RxErrorsRule extends TestWatcher {
35 |
36 | private BlockingDeque errors = new LinkedBlockingDeque<>();
37 |
38 | @Override
39 | protected void starting(Description description) {
40 | RxJavaPlugins.setErrorHandler(t -> errors.add(t));
41 | }
42 |
43 | @Override
44 | protected void finished(Description description) {
45 | RxJavaPlugins.setErrorHandler(null);
46 | }
47 |
48 | public List getErrors() {
49 | return new ArrayList<>(errors);
50 | }
51 |
52 | public Throwable take() {
53 | Throwable error;
54 | try {
55 | error = errors.pollFirst(1, TimeUnit.SECONDS);
56 | } catch (InterruptedException e) {
57 | throw new RuntimeException(e);
58 | }
59 | if (error == null) {
60 | throw new NoSuchElementException("No errors recorded.");
61 | }
62 | return error;
63 | }
64 |
65 | public Throwable takeThrowableFromUndeliverableException() {
66 | Throwable error = take();
67 | assertThat(error).isInstanceOf(UndeliverableException.class);
68 | return error.getCause();
69 | }
70 |
71 | public CompositeException takeCompositeException() {
72 | Throwable error = take();
73 | assertThat(error).isInstanceOf(CompositeException.class);
74 | return (CompositeException) error;
75 | }
76 |
77 | public boolean hasErrors() {
78 | Throwable error = errors.peek();
79 | return error != null;
80 | }
81 |
82 | public void assertNoErrors() {
83 | if (hasErrors()) {
84 | throw new AssertionError("Expected no errors but found " + getErrors());
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/rxdogtag-autodispose/src/main/java/rxdogtag2/autodispose2/AutoDisposeObserverHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2.autodispose2;
17 |
18 | import autodispose2.observers.AutoDisposingCompletableObserver;
19 | import autodispose2.observers.AutoDisposingMaybeObserver;
20 | import autodispose2.observers.AutoDisposingObserver;
21 | import autodispose2.observers.AutoDisposingSingleObserver;
22 | import autodispose2.observers.AutoDisposingSubscriber;
23 | import io.reactivex.rxjava3.core.Completable;
24 | import io.reactivex.rxjava3.core.CompletableObserver;
25 | import io.reactivex.rxjava3.core.Flowable;
26 | import io.reactivex.rxjava3.core.Maybe;
27 | import io.reactivex.rxjava3.core.MaybeObserver;
28 | import io.reactivex.rxjava3.core.Observable;
29 | import io.reactivex.rxjava3.core.Observer;
30 | import io.reactivex.rxjava3.core.Single;
31 | import io.reactivex.rxjava3.core.SingleObserver;
32 | import org.reactivestreams.Subscriber;
33 | import rxdogtag2.ObserverHandler;
34 |
35 | final class AutoDisposeObserverHandler implements ObserverHandler {
36 |
37 | static final AutoDisposeObserverHandler INSTANCE = new AutoDisposeObserverHandler();
38 |
39 | private AutoDisposeObserverHandler() {}
40 |
41 | @Override
42 | public Subscriber handle(Flowable flowable, Subscriber subscriber) {
43 | if (subscriber instanceof AutoDisposingSubscriber) {
44 | return ((AutoDisposingSubscriber) subscriber).delegateSubscriber();
45 | }
46 | return subscriber;
47 | }
48 |
49 | @Override
50 | public Observer handle(Observable observable, Observer observer) {
51 | if (observer instanceof AutoDisposingObserver) {
52 | return ((AutoDisposingObserver) observer).delegateObserver();
53 | }
54 | return observer;
55 | }
56 |
57 | @Override
58 | public MaybeObserver handle(Maybe maybe, MaybeObserver observer) {
59 | if (observer instanceof AutoDisposingMaybeObserver) {
60 | return ((AutoDisposingMaybeObserver) observer).delegateObserver();
61 | }
62 | return observer;
63 | }
64 |
65 | @Override
66 | public SingleObserver handle(Single single, SingleObserver observer) {
67 | if (observer instanceof AutoDisposingSingleObserver) {
68 | return ((AutoDisposingSingleObserver) observer).delegateObserver();
69 | }
70 | return observer;
71 | }
72 |
73 | @Override
74 | public CompletableObserver handle(Completable completable, CompletableObserver observer) {
75 | if (observer instanceof AutoDisposingCompletableObserver) {
76 | return ((AutoDisposingCompletableObserver) observer).delegateObserver();
77 | }
78 | return observer;
79 | }
80 |
81 | @Override
82 | public String toString() {
83 | return "AutoDisposeObserverHandler";
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/AggressiveUncaughtExceptionHandlerRule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import com.google.errorprone.annotations.concurrent.LazyInit;
19 | import java.io.PrintWriter;
20 | import java.io.StringWriter;
21 | import java.lang.Thread.UncaughtExceptionHandler;
22 | import java.util.Map;
23 | import java.util.concurrent.ConcurrentHashMap;
24 | import org.junit.rules.TestWatcher;
25 | import org.junit.runner.Description;
26 | import org.junit.runners.model.Statement;
27 |
28 | /**
29 | * A {@link TestWatcher} used to install an aggressive default {@link UncaughtExceptionHandler}
30 | * similar to the one found on Android. No exceptions should escape from RxDogTag that might cause
31 | * apps to be killed or tests to fail on Android.
32 | */
33 | public final class AggressiveUncaughtExceptionHandlerRule extends TestWatcher {
34 |
35 | @LazyInit private UncaughtExceptionHandler oldDefaultUncaughtExceptionHandler;
36 | @LazyInit private Description lastTestStarted;
37 | private final Map exceptions = new ConcurrentHashMap<>();
38 |
39 | @Override
40 | protected void starting(Description description) {
41 | System.err.println("Installing aggressive uncaught exception handler");
42 | oldDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
43 | Thread.setDefaultUncaughtExceptionHandler(
44 | (thread, throwable) -> {
45 | StringWriter errorText = new StringWriter(256);
46 | errorText.append("Uncaught exception in RxDogTag thread \"");
47 | errorText.append(thread.getName());
48 | errorText.append("\"\n");
49 | throwable.printStackTrace(new PrintWriter(errorText));
50 | errorText.append("\n");
51 | if (lastTestStarted != null) {
52 | errorText.append("Last test to start was: ");
53 | errorText.append(lastTestStarted.getDisplayName());
54 | errorText.append("\n");
55 | }
56 | System.err.print(errorText.toString());
57 |
58 | exceptions.put(throwable, lastTestStarted.getDisplayName());
59 | });
60 | }
61 |
62 | @Override
63 | public Statement apply(Statement base, Description description) {
64 | lastTestStarted = description;
65 | return super.apply(base, description);
66 | }
67 |
68 | @Override
69 | protected void finished(Description description) {
70 | Thread.setDefaultUncaughtExceptionHandler(oldDefaultUncaughtExceptionHandler);
71 | System.err.println("Uninstalled aggressive uncaught exception handler");
72 |
73 | if (!exceptions.isEmpty()) {
74 | sneakyThrow(exceptions.keySet().iterator().next());
75 | }
76 | }
77 |
78 | private static void sneakyThrow(Throwable t) throws T {
79 | //noinspection unchecked
80 | throw (T) t;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/DogTagCompletableObserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import static rxdogtag2.RxDogTag.createException;
19 | import static rxdogtag2.RxDogTag.guardedDelegateCall;
20 | import static rxdogtag2.RxDogTag.reportError;
21 |
22 | import io.reactivex.rxjava3.core.CompletableObserver;
23 | import io.reactivex.rxjava3.disposables.Disposable;
24 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
25 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
26 |
27 | /**
28 | * A delegating {@link CompletableObserver} that throws {@link OnErrorNotImplementedException} with
29 | * stack element tagging to indicate the exact line number that was subscribed.
30 | *
31 | * NOTE: This will also capture exceptions thrown by the {@link #delegate "delegate"} 's
32 | * {@link CompletableObserver#onSubscribe(Disposable) onSubscribe()} or {@link
33 | * CompletableObserver#onComplete()} onComplete()} methods and route them through the more specific
34 | * {@link #onError(Throwable) onError()} in this class for better tagging.
35 | */
36 | final class DogTagCompletableObserver implements CompletableObserver, LambdaConsumerIntrospection {
37 |
38 | private final Throwable t = new Throwable();
39 | private final RxDogTag.Configuration config;
40 | private final CompletableObserver delegate;
41 |
42 | DogTagCompletableObserver(RxDogTag.Configuration config, CompletableObserver delegate) {
43 | this.config = config;
44 | this.delegate = delegate;
45 | }
46 |
47 | @Override
48 | public void onSubscribe(Disposable d) {
49 | if (config.guardObserverCallbacks) {
50 | guardedDelegateCall(
51 | e -> reportError(config, t, e, "onSubscribe"), () -> delegate.onSubscribe(d));
52 | } else {
53 | delegate.onSubscribe(d);
54 | }
55 | }
56 |
57 | @Override
58 | public void onError(Throwable e) {
59 | if (delegate instanceof RxDogTagErrorReceiver) {
60 | if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
61 | delegate.onError(createException(config, t, e, null));
62 | } else if (config.guardObserverCallbacks) {
63 | guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
64 | } else {
65 | delegate.onError(e);
66 | }
67 | } else {
68 | reportError(config, t, e, null);
69 | }
70 | }
71 |
72 | @Override
73 | public void onComplete() {
74 | if (config.guardObserverCallbacks) {
75 | guardedDelegateCall(e -> reportError(config, t, e, "onComplete"), delegate::onComplete);
76 | } else {
77 | delegate.onComplete();
78 | }
79 | }
80 |
81 | @Override
82 | public boolean hasCustomOnError() {
83 | return delegate instanceof LambdaConsumerIntrospection
84 | && ((LambdaConsumerIntrospection) delegate).hasCustomOnError();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/DogTagSingleObserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import static rxdogtag2.RxDogTag.createException;
19 | import static rxdogtag2.RxDogTag.guardedDelegateCall;
20 | import static rxdogtag2.RxDogTag.reportError;
21 |
22 | import io.reactivex.rxjava3.core.SingleObserver;
23 | import io.reactivex.rxjava3.disposables.Disposable;
24 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
25 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
26 |
27 | /**
28 | * A delegating {@link SingleObserver} that throws {@link OnErrorNotImplementedException} with stack
29 | * element tagging to indicate the exact line number that was subscribed.
30 | *
31 | *
NOTE: This will also capture exceptions thrown by the {@link #delegate "delegate"} 's
32 | * {@link SingleObserver#onSubscribe(Disposable) onSubscribe()} or {@link
33 | * SingleObserver#onSuccess(Object)} onNext()} methods and route them through the more specific
34 | * {@link #onError(Throwable) onError()} in this class for better tagging.
35 | *
36 | * @param The type
37 | */
38 | final class DogTagSingleObserver implements SingleObserver, LambdaConsumerIntrospection {
39 |
40 | private final Throwable t = new Throwable();
41 | private final RxDogTag.Configuration config;
42 | private final SingleObserver delegate;
43 |
44 | DogTagSingleObserver(RxDogTag.Configuration config, SingleObserver delegate) {
45 | this.config = config;
46 | this.delegate = delegate;
47 | }
48 |
49 | @Override
50 | public void onSubscribe(Disposable d) {
51 | if (config.guardObserverCallbacks) {
52 | guardedDelegateCall(
53 | e -> reportError(config, t, e, "onSubscribe"), () -> delegate.onSubscribe(d));
54 | } else {
55 | delegate.onSubscribe(d);
56 | }
57 | }
58 |
59 | @Override
60 | public void onSuccess(T t) {
61 | if (config.guardObserverCallbacks) {
62 | guardedDelegateCall(
63 | e -> reportError(config, this.t, e, "onSuccess"), () -> delegate.onSuccess(t));
64 | } else {
65 | delegate.onSuccess(t);
66 | }
67 | }
68 |
69 | @Override
70 | public void onError(Throwable e) {
71 | if (delegate instanceof RxDogTagErrorReceiver) {
72 | if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
73 | delegate.onError(createException(config, t, e, null));
74 | } else if (config.guardObserverCallbacks) {
75 | guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
76 | } else {
77 | delegate.onError(e);
78 | }
79 | } else {
80 | reportError(config, t, e, null);
81 | }
82 | }
83 |
84 | @Override
85 | public boolean hasCustomOnError() {
86 | return delegate instanceof LambdaConsumerIntrospection
87 | && ((LambdaConsumerIntrospection) delegate).hasCustomOnError();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/ObserverHandlerDefaultsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static com.google.common.truth.Truth.assertThat;
19 |
20 | import io.reactivex.rxjava3.core.Completable;
21 | import io.reactivex.rxjava3.core.Flowable;
22 | import io.reactivex.rxjava3.core.Maybe;
23 | import io.reactivex.rxjava3.core.Observable;
24 | import io.reactivex.rxjava3.core.Single;
25 | import io.reactivex.rxjava3.observers.TestObserver;
26 | import io.reactivex.rxjava3.subscribers.TestSubscriber;
27 | import org.junit.Test;
28 | import rxdogtag2.ObserverHandler;
29 | import rxdogtag2.RxDogTag;
30 |
31 | public final class ObserverHandlerDefaultsTest {
32 |
33 | private ObserverHandler handler = new ObserverHandler() {};
34 |
35 | @Test
36 | public void defaultsShouldReturnSameObserver() {
37 | // Using TestObserver out of convenience since it implements every Observer type.
38 | TestObserver o = new TestObserver<>();
39 | TestSubscriber s = new TestSubscriber<>();
40 |
41 | assertThat(handler.handle(Flowable.never(), s)).isSameInstanceAs(s);
42 | assertThat(handler.handle(Observable.never(), o)).isSameInstanceAs(o);
43 | assertThat(handler.handle(Maybe.never(), o)).isSameInstanceAs(o);
44 | assertThat(handler.handle(Single.never(), o)).isSameInstanceAs(o);
45 | assertThat(handler.handle(Completable.never(), o)).isSameInstanceAs(o);
46 | }
47 |
48 | /**
49 | * This is a weird but necessary tests. We have proguard configurations that depend on these
50 | * packages to have names kept in order for DogTagObservers to work their magic correctly.
51 | *
52 | * In the event that this test fails, please update the proguard configurations with the new
53 | * package names. You will see something like this the bundled proguard config.
54 | *
55 | *
56 | * -keeppackagenames io.reactivex**
57 | *
58 | *
59 | * This should be updated with the new package name.
60 | */
61 | @Test
62 | public void verifyPackages() {
63 | checkPackage("Observable", "io.reactivex.rxjava3", "io.reactivex.rxjava3");
64 | checkPackage("RxDogTag", "rxdogtag2", RxDogTag.class.getPackage().getName());
65 | }
66 |
67 | private void checkPackage(String name, String expectedPackage, String actualPackage) {
68 | if (!actualPackage.equals(expectedPackage)) {
69 | throw new RuntimeException(
70 | "The package name for "
71 | + name
72 | + " has changed. Please "
73 | + "update this test and the proguard -keepnames configuration to properly keep the new "
74 | + "package It was \n\"-keepnames class "
75 | + expectedPackage
76 | + ".**\"\nbut should now "
77 | + "be\n\"-keepnames class "
78 | + actualPackage
79 | + ".**\"");
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 | @rem Execute Gradle
88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
89 |
90 | :end
91 | @rem End local scope for the variables with windows NT shell
92 | if "%ERRORLEVEL%"=="0" goto mainEnd
93 |
94 | :fail
95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
96 | rem the _cmd.exe /c_ return code!
97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
98 | exit /b 1
99 |
100 | :mainEnd
101 | if "%OS%"=="Windows_NT" endlocal
102 |
103 | :omega
104 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/DogTagObserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import static rxdogtag2.RxDogTag.createException;
19 | import static rxdogtag2.RxDogTag.guardedDelegateCall;
20 | import static rxdogtag2.RxDogTag.reportError;
21 |
22 | import io.reactivex.rxjava3.core.Observer;
23 | import io.reactivex.rxjava3.disposables.Disposable;
24 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
25 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
26 |
27 | /**
28 | * A delegating {@link Observer} that throws {@link OnErrorNotImplementedException} with stack
29 | * element tagging to indicate the exact line number that was subscribed.
30 | *
31 | *
NOTE: This will also capture exceptions thrown by the {@link #delegate "delegate"} 's
32 | * {@link Observer#onSubscribe(Disposable) onSubscribe()}, {@link Observer#onNext(Object)}
33 | * onNext()}, or {@link Observer#onComplete()} onComplete()} methods and route them through the more
34 | * specific {@link #onError(Throwable) onError()} in this class for better tagging.
35 | *
36 | * @param The type
37 | */
38 | final class DogTagObserver implements Observer, LambdaConsumerIntrospection {
39 |
40 | private final Throwable t = new Throwable();
41 | private final RxDogTag.Configuration config;
42 | private final Observer delegate;
43 |
44 | DogTagObserver(RxDogTag.Configuration config, Observer delegate) {
45 | this.config = config;
46 | this.delegate = delegate;
47 | }
48 |
49 | @Override
50 | public void onSubscribe(Disposable d) {
51 | if (config.guardObserverCallbacks) {
52 | guardedDelegateCall(
53 | e -> reportError(config, t, e, "onSubscribe"), () -> delegate.onSubscribe(d));
54 | } else {
55 | delegate.onSubscribe(d);
56 | }
57 | }
58 |
59 | @Override
60 | public void onNext(T t) {
61 | if (config.guardObserverCallbacks) {
62 | guardedDelegateCall(e -> reportError(config, this.t, e, "onNext"), () -> delegate.onNext(t));
63 | } else {
64 | delegate.onNext(t);
65 | }
66 | }
67 |
68 | @Override
69 | public void onError(Throwable e) {
70 | if (delegate instanceof RxDogTagErrorReceiver) {
71 | if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
72 | delegate.onError(createException(config, t, e, null));
73 | } else if (config.guardObserverCallbacks) {
74 | guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
75 | } else {
76 | delegate.onError(e);
77 | }
78 | } else {
79 | reportError(config, t, e, null);
80 | }
81 | }
82 |
83 | @Override
84 | public void onComplete() {
85 | if (config.guardObserverCallbacks) {
86 | guardedDelegateCall(e -> reportError(config, t, e, "onComplete"), delegate::onComplete);
87 | } else {
88 | delegate.onComplete();
89 | }
90 | }
91 |
92 | @Override
93 | public boolean hasCustomOnError() {
94 | return delegate instanceof LambdaConsumerIntrospection
95 | && ((LambdaConsumerIntrospection) delegate).hasCustomOnError();
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/ObserverHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import io.reactivex.rxjava3.core.Completable;
19 | import io.reactivex.rxjava3.core.CompletableObserver;
20 | import io.reactivex.rxjava3.core.Flowable;
21 | import io.reactivex.rxjava3.core.Maybe;
22 | import io.reactivex.rxjava3.core.MaybeObserver;
23 | import io.reactivex.rxjava3.core.Observable;
24 | import io.reactivex.rxjava3.core.Observer;
25 | import io.reactivex.rxjava3.core.Single;
26 | import io.reactivex.rxjava3.core.SingleObserver;
27 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
28 | import org.reactivestreams.Subscriber;
29 |
30 | /**
31 | * This interface can be used to further decorate or unpack custom subscribers/observers as needed.
32 | *
33 | * Example use cases:
34 | *
35 | *
36 | * Unpacking custom observers to yield their underlying delegate observers or {@link
37 | * LambdaConsumerIntrospection} behavior.
38 | * Decorating observers with custom runtime behavior.
39 | *
40 | *
41 | * Note: The observer returned here will NOT be used for anything other than determining
42 | * if there is custom error handling.
43 | */
44 | public interface ObserverHandler {
45 |
46 | /**
47 | * Callbacks to handle {@link Flowable} and {@link Subscriber}.
48 | *
49 | * @param flowable the source flowable.
50 | * @param subscriber the source subscriber.
51 | * @return possible decorated or unpacked observer.
52 | */
53 | default Subscriber handle(Flowable flowable, Subscriber subscriber) {
54 | return subscriber;
55 | }
56 |
57 | /**
58 | * Callbacks to handle {@link Observable} and {@link Observer}.
59 | *
60 | * @param observable the source observable.
61 | * @param observer the source observer.
62 | * @return possible decorated or unpacked observer.
63 | */
64 | default Observer handle(Observable observable, Observer observer) {
65 | return observer;
66 | }
67 |
68 | /**
69 | * Callbacks to handle {@link Maybe} and {@link MaybeObserver}.
70 | *
71 | * @param maybe the source maybe.
72 | * @param observer the source observer.
73 | * @return possible decorated or unpacked observer.
74 | */
75 | default MaybeObserver handle(Maybe maybe, MaybeObserver observer) {
76 | return observer;
77 | }
78 |
79 | /**
80 | * Callbacks to handle {@link Single} and {@link SingleObserver}.
81 | *
82 | * @param single the source single.
83 | * @param observer the source observer.
84 | * @return possible decorated or unpacked observer.
85 | */
86 | default SingleObserver handle(Single single, SingleObserver observer) {
87 | return observer;
88 | }
89 |
90 | /**
91 | * Callbacks to handle {@link Completable} and {@link CompletableObserver}.
92 | *
93 | * @param completable the source completable.
94 | * @param observer the source observer.
95 | * @return possible decorated or unpacked observer.
96 | */
97 | default CompletableObserver handle(Completable completable, CompletableObserver observer) {
98 | return observer;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/DogTagMaybeObserver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import static rxdogtag2.RxDogTag.createException;
19 | import static rxdogtag2.RxDogTag.guardedDelegateCall;
20 | import static rxdogtag2.RxDogTag.reportError;
21 |
22 | import io.reactivex.rxjava3.core.MaybeObserver;
23 | import io.reactivex.rxjava3.disposables.Disposable;
24 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
25 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
26 |
27 | /**
28 | * A delegating {@link MaybeObserver} that throws {@link OnErrorNotImplementedException} with stack
29 | * element tagging to indicate the exact line number that was subscribed.
30 | *
31 | *
NOTE: This will also capture exceptions thrown by the {@link #delegate "delegate"} 's
32 | * {@link MaybeObserver#onSubscribe(Disposable) onSubscribe()}, {@link
33 | * MaybeObserver#onSuccess(Object)} onNext()}, or {@link MaybeObserver#onComplete()} onComplete()}
34 | * methods and route them through the more specific {@link #onError(Throwable) onError()} in this
35 | * class for better tagging.
36 | *
37 | * @param The type
38 | */
39 | final class DogTagMaybeObserver implements MaybeObserver, LambdaConsumerIntrospection {
40 |
41 | private final Throwable t = new Throwable();
42 | private final RxDogTag.Configuration config;
43 | private final MaybeObserver delegate;
44 |
45 | DogTagMaybeObserver(RxDogTag.Configuration config, MaybeObserver delegate) {
46 | this.config = config;
47 | this.delegate = delegate;
48 | }
49 |
50 | @Override
51 | public void onSubscribe(Disposable d) {
52 | if (config.guardObserverCallbacks) {
53 | guardedDelegateCall(
54 | e -> reportError(config, t, e, "onSubscribe"), () -> delegate.onSubscribe(d));
55 | } else {
56 | delegate.onSubscribe(d);
57 | }
58 | }
59 |
60 | @Override
61 | public void onSuccess(T t) {
62 | if (config.guardObserverCallbacks) {
63 | guardedDelegateCall(
64 | e -> reportError(config, this.t, e, "onSuccess"), () -> delegate.onSuccess(t));
65 | } else {
66 | delegate.onSuccess(t);
67 | }
68 | }
69 |
70 | @Override
71 | public void onError(Throwable e) {
72 | if (delegate instanceof RxDogTagErrorReceiver) {
73 | if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
74 | delegate.onError(createException(config, t, e, null));
75 | } else if (config.guardObserverCallbacks) {
76 | guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
77 | } else {
78 | delegate.onError(e);
79 | }
80 | } else {
81 | reportError(config, t, e, null);
82 | }
83 | }
84 |
85 | @Override
86 | public void onComplete() {
87 | if (config.guardObserverCallbacks) {
88 | guardedDelegateCall(e -> reportError(config, t, e, "onComplete"), delegate::onComplete);
89 | } else {
90 | delegate.onComplete();
91 | }
92 | }
93 |
94 | @Override
95 | public boolean hasCustomOnError() {
96 | return delegate instanceof LambdaConsumerIntrospection
97 | && ((LambdaConsumerIntrospection) delegate).hasCustomOnError();
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/rxdogtag/src/main/java/rxdogtag2/DogTagSubscriber.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rxdogtag2;
17 |
18 | import static rxdogtag2.RxDogTag.createException;
19 | import static rxdogtag2.RxDogTag.guardedDelegateCall;
20 | import static rxdogtag2.RxDogTag.reportError;
21 |
22 | import io.reactivex.rxjava3.core.FlowableSubscriber;
23 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
24 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
25 | import org.reactivestreams.Subscriber;
26 | import org.reactivestreams.Subscription;
27 |
28 | /**
29 | * A delegating {@link Subscriber} that throws {@link OnErrorNotImplementedException} with stack
30 | * element tagging to indicate the exact line number that was subscribed.
31 | *
32 | * NOTE: This will also capture exceptions thrown by the {@link #delegate "delegate"} 's
33 | * {@link Subscriber#onSubscribe(Subscription) onSubscribe()}, {@link Subscriber#onNext(Object)}
34 | * onNext()}, or {@link Subscriber#onComplete()} onComplete()} methods and route them through the
35 | * more specific {@link #onError(Throwable) onError()} in this class for better tagging.
36 | *
37 | *
Due to restrictions in the ReactiveStreams spec, delegate exception capturing will only work
38 | * on the more lenient {@link FlowableSubscriber}, which allows callback exceptions to be rerouted
39 | * through {@link #onError(Throwable) onError()}. This is also why this class implements that
40 | * interface.
41 | *
42 | * @param The type
43 | */
44 | final class DogTagSubscriber implements FlowableSubscriber, LambdaConsumerIntrospection {
45 |
46 | private final Throwable t = new Throwable();
47 | private final RxDogTag.Configuration config;
48 | private final Subscriber delegate;
49 |
50 | DogTagSubscriber(RxDogTag.Configuration config, Subscriber delegate) {
51 | this.config = config;
52 | this.delegate = delegate;
53 | }
54 |
55 | @Override
56 | public void onSubscribe(Subscription s) {
57 | if (config.guardObserverCallbacks) {
58 | guardedDelegateCall(
59 | e -> reportError(config, t, e, "onSubscribe"), () -> delegate.onSubscribe(s));
60 | } else {
61 | delegate.onSubscribe(s);
62 | }
63 | }
64 |
65 | @Override
66 | public void onNext(T t) {
67 | if (config.guardObserverCallbacks) {
68 | guardedDelegateCall(e -> reportError(config, this.t, e, "onNext"), () -> delegate.onNext(t));
69 | } else {
70 | delegate.onNext(t);
71 | }
72 | }
73 |
74 | @Override
75 | public void onError(Throwable e) {
76 | if (delegate instanceof RxDogTagErrorReceiver) {
77 | if (delegate instanceof RxDogTagTaggedExceptionReceiver) {
78 | delegate.onError(createException(config, t, e, null));
79 | } else if (config.guardObserverCallbacks) {
80 | guardedDelegateCall(e2 -> reportError(config, t, e2, "onError"), () -> delegate.onError(e));
81 | } else {
82 | delegate.onError(e);
83 | }
84 | } else {
85 | reportError(config, t, e, null);
86 | }
87 | }
88 |
89 | @Override
90 | public void onComplete() {
91 | if (config.guardObserverCallbacks) {
92 | guardedDelegateCall(e -> reportError(config, t, e, "onComplete"), delegate::onComplete);
93 | } else {
94 | delegate.onComplete();
95 | }
96 | }
97 |
98 | @Override
99 | public boolean hasCustomOnError() {
100 | return delegate instanceof LambdaConsumerIntrospection
101 | && ((LambdaConsumerIntrospection) delegate).hasCustomOnError();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/docs/benchmark.md:
--------------------------------------------------------------------------------
1 | # Benchmarks
2 |
3 | ## Running the benchmark
4 |
5 | You can run the benchmark by executing `./gradlew :android-benchmark:benchmark:connectedCheck`. Note that the benchmark should be run on an actual device
6 |
7 | You can then take the output from the benchmark and run it through `DataParser.kt` to get a structured breakdown as seen below.
8 |
9 | It's important to look at the units. In general, RxDogTag does add some overhead to your RxJava subscriptions but that overhead is irrelevant in the larger execution context of the code around it (less than a millisecond in most cases).
10 |
11 | Running the benchmark on a [Pixel 3](https://store.google.com/product/pixel_3_specs) yields the below results.
12 |
13 | ## Event throughput: grouped by number of events
14 |
15 | _Measures the amount of time it takes for given number of elements to pass through the stream._
16 |
17 | ### Simple: 1 item (Observable)
18 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
19 | |----------|----------|------------|-----------|
20 | | false | false | 0.001ms | 532ns |
21 | | true | false | 0.007ms | 7,060ns |
22 | | true | true | 0.007ms | 7,378ns |
23 |
24 | ### Complex: 1 item (Observable)
25 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
26 | |----------|----------|------------|-----------|
27 | | true | false | 0.049ms | 49,195ns |
28 | | true | true | 0.050ms | 50,492ns |
29 | | false | false | 0.054ms | 53,672ns |
30 |
31 | ### Simple: 1_000 items (Observable)
32 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
33 | |----------|----------|------------|-----------|
34 | | false | false | 0.024ms | 23,968ns |
35 | | true | false | 0.041ms | 40,743ns |
36 | | true | true | 0.153ms | 152,943ns |
37 |
38 | ### Complex: 1_000 items (Observable)
39 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
40 | |----------|----------|------------|-----------|
41 | | false | false | 0.291ms | 291,301ns |
42 | | true | false | 0.313ms | 312,864ns |
43 | | true | true | 0.313ms | 313,334ns |
44 |
45 | ### Simple: 1_000_000 items (Observable)
46 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
47 | |----------|----------|------------|-----------|
48 | | false | false | 23.994ms | 23,993,700ns |
49 | | true | false | 27.305ms | 27,304,847ns |
50 | | true | true | 166.887ms | 166,887,047ns |
51 |
52 | ### Complex: 1_000_000 items (Observable)
53 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
54 | |----------|----------|------------|-----------|
55 | | true | false | 249.740ms | 249,739,764ns |
56 | | false | false | 252.728ms | 252,727,577ns |
57 | | true | true | 257.554ms | 257,553,671ns |
58 |
59 | ### Simple: 1 item (Flowable)
60 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
61 | |----------|----------|------------|-----------|
62 | | false | false | 0.001ms | 519ns |
63 | | true | false | 0.007ms | 7,234ns |
64 | | true | true | 0.008ms | 7,581ns |
65 |
66 | ### Complex: 1 item (Flowable)
67 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
68 | |----------|----------|------------|-----------|
69 | | true | false | 0.050ms | 50,081ns |
70 | | true | true | 0.051ms | 50,787ns |
71 | | false | false | 0.056ms | 56,004ns |
72 |
73 | ### Simple: 1_000 items (Flowable)
74 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
75 | |----------|----------|------------|-----------|
76 | | false | false | 0.025ms | 24,920ns |
77 | | true | false | 0.041ms | 40,568ns |
78 | | true | true | 0.153ms | 153,124ns |
79 |
80 | ### Complex: 1_000 items (Flowable)
81 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
82 | |----------|----------|------------|-----------|
83 | | false | false | 0.273ms | 273,178ns |
84 | | true | true | 0.343ms | 342,812ns |
85 | | true | false | 0.375ms | 375,208ns |
86 |
87 | ### Simple: 1_000_000 items (Flowable)
88 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
89 | |----------|----------|------------|-----------|
90 | | false | false | 23.953ms | 23,952,919ns |
91 | | true | false | 26.792ms | 26,791,825ns |
92 | | true | true | 162.547ms | 162,547,359ns |
93 |
94 | ### Complex: 1_000_000 items (Flowable)
95 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
96 | |----------|----------|------------|-----------|
97 | | true | true | 300.186ms | 300,186,228ns |
98 | | true | false | 302.881ms | 302,880,498ns |
99 | | false | false | 304.952ms | 304,952,062ns |
100 |
101 |
102 | ## Subscribe cost: grouped by complexity
103 |
104 | _Measures the cost to subscription incurred by RxDogTag. Subscription means no emissions, subscription only._
105 |
106 | ### Simple (Observable)
107 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
108 | |----------|----------|------------|-----------|
109 | | false | false | 0.000ms | 331ns |
110 | | true | false | 0.007ms | 7,275ns |
111 |
112 | ### Simple (Flowable)
113 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
114 | |----------|----------|------------|-----------|
115 | | false | false | 0.000ms | 365ns |
116 | | true | false | 0.008ms | 7,506ns |
117 |
118 | ### Complex (Observable)
119 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
120 | |----------|----------|------------|-----------|
121 | | false | false | 0.002ms | 1,673ns |
122 | | true | false | 0.010ms | 9,770ns |
123 |
124 | ### Complex (Flowable)
125 | | RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |
126 | |----------|----------|------------|-----------|
127 | | false | false | 0.006ms | 5,741ns |
128 | | true | false | 0.021ms | 20,783ns |
129 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 | # Determine the Java command to use to start the JVM.
86 | if [ -n "$JAVA_HOME" ] ; then
87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
88 | # IBM's JDK on AIX uses strange locations for the executables
89 | JAVACMD="$JAVA_HOME/jre/sh/java"
90 | else
91 | JAVACMD="$JAVA_HOME/bin/java"
92 | fi
93 | if [ ! -x "$JAVACMD" ] ; then
94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
95 |
96 | Please set the JAVA_HOME variable in your environment to match the
97 | location of your Java installation."
98 | fi
99 | else
100 | JAVACMD="java"
101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
102 |
103 | Please set the JAVA_HOME variable in your environment to match the
104 | location of your Java installation."
105 | fi
106 |
107 | # Increase the maximum file descriptors if we can.
108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
109 | MAX_FD_LIMIT=`ulimit -H -n`
110 | if [ $? -eq 0 ] ; then
111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
112 | MAX_FD="$MAX_FD_LIMIT"
113 | fi
114 | ulimit -n $MAX_FD
115 | if [ $? -ne 0 ] ; then
116 | warn "Could not set maximum file descriptor limit: $MAX_FD"
117 | fi
118 | else
119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
120 | fi
121 | fi
122 |
123 | # For Darwin, add options to specify how the application appears in the dock
124 | if $darwin; then
125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
126 | fi
127 |
128 | # For Cygwin or MSYS, switch paths to Windows format before running java
129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
132 | JAVACMD=`cygpath --unix "$JAVACMD"`
133 |
134 | # We build the pattern for arguments to be converted via cygpath
135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
136 | SEP=""
137 | for dir in $ROOTDIRSRAW ; do
138 | ROOTDIRS="$ROOTDIRS$SEP$dir"
139 | SEP="|"
140 | done
141 | OURCYGPATTERN="(^($ROOTDIRS))"
142 | # Add a user-defined pattern to the cygpath arguments
143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
145 | fi
146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
147 | i=0
148 | for arg in "$@" ; do
149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
151 |
152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
154 | else
155 | eval `echo args$i`="\"$arg\""
156 | fi
157 | i=`expr $i + 1`
158 | done
159 | case $i in
160 | 0) set -- ;;
161 | 1) set -- "$args0" ;;
162 | 2) set -- "$args0" "$args1" ;;
163 | 3) set -- "$args0" "$args1" "$args2" ;;
164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
170 | esac
171 | fi
172 |
173 | # Escape application args
174 | save () {
175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
176 | echo " "
177 | }
178 | APP_ARGS=`save "$@"`
179 |
180 | # Collect all arguments for the java command, following the shell quoting and substitution rules
181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
182 |
183 | exec "$JAVACMD" "$@"
184 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | Version 2.0.2
5 | -------------
6 |
7 | _2023-03-09_
8 |
9 | Fix embedded proguard rules to use `-keepnames` instead of `-keeppackagenames` as the latter isn't respected by R8 3+ when `repackageclasses` is enabled.
10 |
11 | Thanks to [@viakunin](https://github.com/viakunin) for contributing this fix!
12 |
13 | Version 1.0.2
14 | -------------
15 |
16 | _2023-03-09_
17 |
18 | Same proguard changes as 2.0.2 but for RxJava 2.x/RxDogTag 1.x.
19 |
20 | Version 2.0.1
21 | -------------
22 |
23 | _2020-10-26_
24 |
25 | Fix embedded proguard rules to completely keep `subscribe()` methods and rxdogtag code. R8 may
26 | inline `subscribe()` calls entirely to their call-sites, which breaks RxDogTag's tagging.
27 |
28 | While this blanket keep is sort of a cardinal sin of libraries to do this, RxDogTag is only a few
29 | classes and incredibly small, so we claim this is a small price to pay for actionable stacktraces.
30 |
31 | Version 1.0.1
32 | -------------
33 |
34 | _2020-10-26_
35 |
36 | Same proguard changes as 2.0.1 but for RxJava 2.x/RxDogTag 1.x.
37 |
38 | Version 2.0.0
39 | -------------
40 |
41 | _2020-03-08_
42 |
43 | RxDogTag 2 is built against RxJava 3 and is binary-compatible with RxDogTag 1.x and RxJava 2.x. As such - it has a different package name and maven group ID.
44 |
45 | ### Packaging
46 |
47 | | | Maven Group ID | Package Name |
48 | | --- | --- | --- |
49 | | 1.x | `com.uber.rxdogtag` | `com.uber.rxdogtag` |
50 | | 2.x | `com.uber.rxdogtag2` | `rxdogtag2` |
51 |
52 | For any sub-packages, the above mapping should be used for those package prefix replacements as well.
53 |
54 | ### Changes
55 |
56 | * Dependency Upgrades
57 | ```
58 | RxJava 3.0.0
59 | AutoDispose 2.0.0
60 | ```
61 |
62 | Version 1.0.0
63 | -------------
64 |
65 | _2020-03-07_
66 |
67 | * Stable release!
68 | * This release is identical in functionality to 0.3.0.
69 | * Various dependency upgrades. ([#55](https://github.com/uber/RxDogTag/pull/55))
70 | ```
71 | RxJava 2.2.18
72 | AutoDispose 1.4.0
73 | ```
74 |
75 | Version 0.3.0
76 | -------------
77 |
78 | _2019-09-23_
79 |
80 | **New: Builder option to disable guarded observer callbacks. [#43](https://github.com/uber/RxDogTag/pull/43)**
81 |
82 | By default, RxDogTag will try/catch every onNext/onSuccess/onComplete calls in its observers to try to catch exceptions
83 | and modify the trace before they're handed to the (unhandled) onError. If you only want to handle
84 | upstream errors, you can disable this behavior in the builder now. This can be useful in hot paths
85 | where performance is more of a concern.
86 |
87 | ```java
88 | RxDogTag.builder()
89 | .guardObserverCallbacks(false)
90 | .install();
91 | ```
92 |
93 | **New: ErrorReceivers API. [#45](https://github.com/uber/RxDogTag/pull/45)**
94 |
95 | For users that have custom onError() implementations but want to supplement it with RxDogTag, there are
96 | two new APIs to help with this.
97 |
98 | `RxDogTagErrorReceiver` is a simple interface that your custom observer can implement to indicate that
99 | it wants its onError() method to be called with the aforementioned guarded observer callback. Note
100 | however that modified tagged exceptions in this case will be delivered to `RxJavaPlugins.onError()`.
101 |
102 | `RxDogTagTaggedExceptionReceiver` is an interface that your custom observer can implement to indicate
103 | that it wants its onError() called with the modified tagged exception. This is useful if you have
104 | custom onError() handling but want to use/report RxDogTag's tagged information for something custom.
105 |
106 | **Misc**
107 | - Proguard file has been tuned to use the more idiomatic `-keeppackagenames`. [#29](https://github.com/uber/RxDogTag/pull/29)
108 | - We have a website now! https://uber.github.io/RxDogTag
109 | - We've been doing a lot of work to add thorough benchmarks for RxDogTag. A dedicated section can be
110 | found on the website at https://uber.github.io/RxDogTag/benchmark.
111 |
112 | Special thanks to [@emartynov](https://github.com/emartynov) for contributing to this release!
113 |
114 | Version 0.2.0
115 | -------------
116 |
117 | _2019-05-14_
118 |
119 | **New: Builder-based API for configuration**
120 |
121 | RxDogTag now uses a builder API for configuration. Currently this includes the existing `ObserverHandler`
122 | and package whitelisting, as well as a new configuration to optionally disable stacktrace annotations.
123 |
124 | ```java
125 | RxDogTag.builder()
126 | .disableAnnotations()
127 | .addObserverHandlers(...)
128 | .addIgnoredPackages(...)
129 | .configureWith(...) // For other custom AutoDispose.Configurers
130 | .install();
131 | ```
132 |
133 | Note: No-config `RxDogTag.install()` still exists, but is now just a proxy to
134 | `RxDogTag.builder().install()`
135 |
136 | Breaking changes:
137 | * `ObserverHandler` no longer handles package whitelisting, as this is now moved to
138 | the builder API.
139 | * The AutoDispose artifact now uses the builder API's `configureWith` support, and
140 | `AutoDisposeObserverHandler` is now just a package-private implementation detail.
141 |
142 | ```java
143 | RxDogTag.builder()
144 | .configureWith(AutoDisposeConfigurer::configure)
145 | .install();
146 | ```
147 |
148 | **New: Inferred subscribe point is now the first element in the stacktrace.**
149 |
150 | After discussions with others in the community, the inferred subscribe point is now the first element
151 | in the stacktrace for better grouping with crash reporters (which may have otherwise accidentally grouped
152 | the stacktrace header annotations as the "same" crash). This plus some arrow characters means the stacktraces
153 | look like this:
154 |
155 | ```
156 | io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value.
157 |
158 | Caused by: java.lang.NullPointerException: The mapper function returned a null value.
159 | at com.uber.anotherpackage.ReadMeExample.complex(ReadMeExample.java:55)
160 | at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
161 | at [[ ↓↓ Original trace ↓↓ ]].(:0)
162 | at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
163 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
164 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
165 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
166 | // ... and so on
167 | ```
168 |
169 |
170 |
171 | Version 0.1.0
172 | -------------
173 |
174 | _2019-04-09_
175 |
176 | Initial release!
177 |
178 |
--------------------------------------------------------------------------------
/rxdogtag-autodispose/src/test/java/anotherpackage/AutoDisposeObserverHandlerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static anotherpackage.DogTagTestUtil.getPreviousLineNumber;
19 | import static autodispose2.AutoDispose.autoDisposable;
20 | import static com.google.common.truth.Truth.assertThat;
21 |
22 | import autodispose2.AutoDispose;
23 | import autodispose2.CompletableSubscribeProxy;
24 | import autodispose2.FlowableSubscribeProxy;
25 | import autodispose2.MaybeSubscribeProxy;
26 | import autodispose2.ObservableSubscribeProxy;
27 | import autodispose2.ScopeProvider;
28 | import autodispose2.SingleSubscribeProxy;
29 | import io.reactivex.rxjava3.core.Completable;
30 | import io.reactivex.rxjava3.core.Flowable;
31 | import io.reactivex.rxjava3.core.Maybe;
32 | import io.reactivex.rxjava3.core.Observable;
33 | import io.reactivex.rxjava3.core.Single;
34 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
35 | import org.junit.After;
36 | import org.junit.Before;
37 | import org.junit.Rule;
38 | import org.junit.Test;
39 | import rxdogtag2.RxDogTag;
40 | import rxdogtag2.autodispose2.AutoDisposeConfigurer;
41 |
42 | /**
43 | * NOTE: These tests are a little odd. There are two conditions for them running correctly because
44 | * they verify via inspecting stacktraces. 1. the throwError() method MUST calculate the line number
45 | * immediately after subscribe (on the next line). 2. it must not be in the rxdogtag2 package
46 | * because that is filtered out in stacktrace inspection.
47 | */
48 | public final class AutoDisposeObserverHandlerTest implements DogTagTest {
49 |
50 | @Rule public RxErrorsRule errorsRule = new RxErrorsRule();
51 |
52 | @Before
53 | public void setUp() {
54 | RxDogTag.builder().configureWith(AutoDisposeConfigurer::configure).install();
55 | }
56 |
57 | @After
58 | public void tearDown() {
59 | RxDogTag.reset();
60 | }
61 |
62 | @Test
63 | public void testObservable_autodispose() {
64 | Exception original = new RuntimeException("Blah");
65 | assertRewrittenStacktrace(
66 | subscribeError(Observable.error(original).to(autoDisposable(ScopeProvider.UNBOUND))),
67 | original);
68 | }
69 |
70 | @Test
71 | public void testFlowable_autodispose() {
72 | Exception original = new RuntimeException("Blah");
73 | assertRewrittenStacktrace(
74 | subscribeError(Flowable.error(original).to(autoDisposable(ScopeProvider.UNBOUND))),
75 | original);
76 | }
77 |
78 | @Test
79 | public void testSingle_autodispose() {
80 | Exception original = new RuntimeException("Blah");
81 | assertRewrittenStacktrace(
82 | subscribeError(Single.error(original).to(autoDisposable(ScopeProvider.UNBOUND))), original);
83 | }
84 |
85 | @Test
86 | public void testMaybe_autodispose() {
87 | Exception original = new RuntimeException("Blah");
88 | assertRewrittenStacktrace(
89 | subscribeError(Maybe.error(original).to(autoDisposable(ScopeProvider.UNBOUND))), original);
90 | }
91 |
92 | @Test
93 | public void testCompletable_autodispose() {
94 | Exception original = new RuntimeException("Blah");
95 | assertRewrittenStacktrace(
96 | subscribeError(Completable.error(original).to(autoDisposable(ScopeProvider.UNBOUND))),
97 | original);
98 | }
99 |
100 | /** This tests that the original stacktrace was rewritten with the relevant source information. */
101 | private void assertRewrittenStacktrace(int expectedLineNumber, Exception original) {
102 | Throwable e = errorsRule.take();
103 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
104 | assertThat(e).hasMessageThat().isEqualTo(original.getMessage());
105 | assertThat(e.getStackTrace()).isEmpty();
106 | Throwable cause = e.getCause();
107 | assertThat(cause.getStackTrace()[0].getFileName())
108 | .isEqualTo(getClass().getSimpleName() + ".java");
109 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(expectedLineNumber);
110 | assertThat(cause.getStackTrace()[1].getClassName())
111 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
112 | assertThat(cause.getStackTrace()[2].getClassName())
113 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
114 | }
115 |
116 | /**
117 | * This is a weird but necessary tests. We have proguard configurations that depend on these
118 | * packages to have names kept in order for DogTagObservers to work their magic correctly.
119 | *
120 | * In the event that this test fails, please update the proguard configurations with the new
121 | * package names. You will see something like this in the bundled proguard config.
122 | *
123 | *
124 | * -keepnames class com.uber.autodispose.**
125 | *
126 | *
127 | * This should be updated with the new package name.
128 | */
129 | @Test
130 | public void verifyPackages() {
131 | checkPackage("AutoDispose", "autodispose2", AutoDispose.class.getPackage().getName());
132 | }
133 |
134 | private void checkPackage(String name, String expectedPackage, String actualPackage) {
135 | if (!actualPackage.equals(expectedPackage)) {
136 | throw new RuntimeException(
137 | "The package name for "
138 | + name
139 | + " has changed. Please "
140 | + "update this test and the proguard -keepnames configuration to properly keep the new "
141 | + "package It was \n\"-keepnames class "
142 | + expectedPackage
143 | + ".**\"\nbut should now "
144 | + "be\n\"-keepnames class "
145 | + actualPackage
146 | + ".**\"");
147 | }
148 | }
149 |
150 | private static int subscribeError(CompletableSubscribeProxy completable) {
151 | completable.subscribe();
152 | return getPreviousLineNumber();
153 | }
154 |
155 | private static int subscribeError(ObservableSubscribeProxy observable) {
156 | observable.subscribe();
157 | return getPreviousLineNumber();
158 | }
159 |
160 | private static int subscribeError(MaybeSubscribeProxy maybe) {
161 | maybe.subscribe();
162 | return getPreviousLineNumber();
163 | }
164 |
165 | private static int subscribeError(SingleSubscribeProxy single) {
166 | single.subscribe();
167 | return getPreviousLineNumber();
168 | }
169 |
170 | private static int subscribeError(FlowableSubscribeProxy flowable) {
171 | flowable.subscribe();
172 | return getPreviousLineNumber();
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/android-benchmark/benchmark/src/androidTest/kotlin/rxdogtag2/androidbenchmark/RxDogTagAndroidPerf.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2019 Uber Technologies, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package rxdogtag2.androidbenchmark
18 |
19 | import androidx.benchmark.junit4.BenchmarkRule
20 | import androidx.benchmark.junit4.measureRepeated
21 | import androidx.test.filters.LargeTest
22 | import rxdogtag2.RxDogTag
23 | import io.reactivex.rxjava3.core.Flowable
24 | import io.reactivex.rxjava3.core.Observable
25 | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
26 | import io.reactivex.rxjava3.disposables.Disposable
27 | import io.reactivex.rxjava3.functions.Action
28 | import io.reactivex.rxjava3.functions.Consumer
29 | import io.reactivex.rxjava3.schedulers.Schedulers
30 | import org.junit.After
31 | import org.junit.Before
32 | import org.junit.Rule
33 | import org.junit.Test
34 | import org.junit.runner.RunWith
35 | import org.junit.runners.Parameterized
36 | import org.junit.runners.Parameterized.Parameters
37 | import java.util.concurrent.CountDownLatch
38 |
39 | /**
40 | * Measures the time it takes to both subscribe and consume events. Run this like a normal Android
41 | * instrumentation test on a device. Note that benchmarks should be run on a real device for more
42 | * realistic real-world results. There are further configurations that can be used for the Androidx
43 | * Benchmark library used here, full documentation can be found at
44 | * https://developer.android.com/studio/profile/benchmark.html.
45 | *
46 | * Basic command line usage is just to run `./gradlew :android-benchmark:benchmark:connectedCheck`
47 | */
48 | @LargeTest
49 | @RunWith(Parameterized::class)
50 | class RxDogTagAndroidPerf(
51 | private val enabled: Boolean,
52 | private val times: Int,
53 | private val guardedDelegateEnabled: Boolean
54 | ) {
55 |
56 | companion object {
57 | @JvmStatic
58 | @Parameters(name = "enabled={0},times={1},guardedDelegateEnabled={2}")
59 | fun data(): List> {
60 | val list = mutableListOf>()
61 | for (enabled in ENABLED) {
62 | for (times in ITERATIONS) {
63 | for (guardedDelegateEnabled in GUARDED_DELEGATE_ENABLED) {
64 | @Suppress("ControlFlowWithEmptyBody") // Readability
65 | if ((!enabled && guardedDelegateEnabled) || (guardedDelegateEnabled && times == 0)) {
66 | // Skip these configurations since they're irrelevant
67 | } else {
68 | list.add(arrayOf(enabled, times, guardedDelegateEnabled))
69 | }
70 | }
71 | }
72 | }
73 | return list.toList()
74 | }
75 |
76 | private val ITERATIONS = setOf(0, 1, 1_000, 1_000_000)
77 | private val ENABLED = setOf(true, false)
78 | private val GUARDED_DELEGATE_ENABLED = setOf(true, false)
79 | }
80 |
81 | @get:Rule
82 | val benchmarkRule = BenchmarkRule()
83 |
84 | @Before
85 | fun setup() {
86 | Schedulers.shutdown()
87 | Schedulers.start()
88 | if (enabled) {
89 | RxDogTag.builder()
90 | .apply {
91 | if (!guardedDelegateEnabled) {
92 | guardObserverCallbacks(false)
93 | }
94 | }
95 | .install()
96 | }
97 | }
98 |
99 | @After
100 | fun tearDown() {
101 | RxDogTag.reset()
102 | Schedulers.shutdown()
103 | }
104 |
105 | @Test
106 | fun flowable_simple() {
107 | val flowable = flowableInstance(times)
108 | if (times == 0) {
109 | benchmarkRule.measureRepeated {
110 | disposeWithTimingDisabled(flowable.subscribe())
111 | }
112 | } else {
113 | benchmarkRule.measureRepeated {
114 | latchConsumer { flowable.subscribe(it) }
115 | }
116 | }
117 | }
118 |
119 | @Test
120 | fun observable_simple() {
121 | val observable = observableInstance(times)
122 | if (times == 0) {
123 | benchmarkRule.measureRepeated {
124 | disposeWithTimingDisabled(observable.subscribe())
125 | }
126 | } else {
127 | benchmarkRule.measureRepeated {
128 | latchConsumer { observable.subscribe(it) }
129 | }
130 | }
131 | }
132 |
133 | @Test
134 | fun flowable_complex() {
135 | val flowable = flowableInstance(times)
136 | .filter { true }
137 | .map { it * 2 }
138 | .subscribeOn(Schedulers.trampoline())
139 | .observeOn(AndroidSchedulers.mainThread())
140 | .takeUntil(Flowable.never())
141 | .ignoreElements()
142 | if (times == 0) {
143 | benchmarkRule.measureRepeated {
144 | disposeWithTimingDisabled(flowable.subscribe())
145 | }
146 | } else {
147 | benchmarkRule.measureRepeated {
148 | latchAction { flowable.subscribe(it) }
149 | }
150 | }
151 | }
152 |
153 | @Test
154 | fun observable_complex() {
155 | val observable = observableInstance(times)
156 | .filter { true }
157 | .map { it * 2 }
158 | .subscribeOn(Schedulers.trampoline())
159 | .observeOn(AndroidSchedulers.mainThread())
160 | .takeUntil(Observable.never())
161 | .ignoreElements()
162 | if (times == 0) {
163 | benchmarkRule.measureRepeated {
164 | disposeWithTimingDisabled(observable.subscribe())
165 | }
166 | } else {
167 | benchmarkRule.measureRepeated {
168 | latchAction { observable.subscribe(it) }
169 | }
170 | }
171 | }
172 |
173 | @Suppress("NOTHING_TO_INLINE") // Inlined for test overhead reasons
174 | private inline fun BenchmarkRule.Scope.disposeWithTimingDisabled(disposable: Disposable) {
175 | runWithTimingDisabled { disposable.dispose() }
176 | }
177 |
178 | private inline fun BenchmarkRule.Scope.latchAction(onComplete: (Action) -> Unit) {
179 | val latch = runWithTimingDisabled { CountDownLatch(1) }
180 | onComplete(Action { latch.countDown() })
181 | latch.await()
182 | }
183 |
184 | private inline fun BenchmarkRule.Scope.latchConsumer(onComplete: (Consumer) -> Unit) {
185 | val latch = runWithTimingDisabled { CountDownLatch(1) }
186 | onComplete(Consumer { latch.countDown() })
187 | latch.await()
188 | }
189 |
190 | /**
191 | * @param times -1 for empty, 0 for never, otherwise that number of emissions
192 | */
193 | private fun flowableInstance(times: Int): Flowable {
194 | if (times == 0) {
195 | return Flowable.never()
196 | } else if (times == -1) {
197 | return Flowable.empty()
198 | }
199 | return Flowable.fromArray(*Array(times) { 777 })
200 | }
201 |
202 | /**
203 | * @param times -1 for empty, 0 for never, otherwise that number of emissions
204 | */
205 | private fun observableInstance(times: Int): Observable {
206 | return when (times) {
207 | 0 -> Observable.never()
208 | -1 -> Observable.empty()
209 | else -> Observable.fromArray(*Array(times) { 777 })
210 | }
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RxDogTag
2 |
3 | RxDogTag is a utility to tag originating subscribe points in RxJava 2+ observers, with the goal of
4 | surfacing their subscribe locations for error reporting/investigation later in the event of an unhandled
5 | error. This is _only_ for RxJava observers that do not implement `onError()`.
6 |
7 | ## Download
8 |
9 | If you're targeting RxJava 2:
10 |
11 | [](https://mvnrepository.com/artifact/com.uber.rxdogtag/rxdogtag)
12 |
13 | ```gradle
14 | implementation("com.uber.rxdogtag:rxdogtag:x.y.z")
15 | ```
16 |
17 | If you're targeting RxJava 3:
18 |
19 | [](https://mvnrepository.com/artifact/com.uber.rxdogtag2/rxdogtag)
20 |
21 | ```gradle
22 | implementation("com.uber.rxdogtag2:rxdogtag:x.y.z")
23 | ```
24 |
25 | # Setup
26 |
27 | Install early in your application lifecycle via `RxDogTag.install()`. This will install the necessary
28 | hooks in `RxJavaPlugins`. Note that these will replace any existing plugins at the hooks it uses. See
29 | the [JavaDoc](https://uber.github.io/RxDogTag/1.x/rxdogtag/com.uber.rxdogtag/-rx-dog-tag/install/) for full details of which plugins it uses.
30 |
31 | ## Example
32 |
33 | Consider the following classic RxJava error:
34 |
35 | ```java
36 | Observable.range(0, 10)
37 | .subscribeOn(Schedulers.io())
38 | .map(i -> null)
39 | .subscribe();
40 | ```
41 |
42 | This is a fairly common case in RxJava concurrency. Without tagging, this yields the following trace:
43 |
44 | ```
45 | io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | The mapper function returned a null value.
46 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
47 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
48 | at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
49 | at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
50 | at io.reactivex.internal.observers.BasicFuseableObserver.fail(BasicFuseableObserver.java:110)
51 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
52 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
53 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
54 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
55 | at io.reactivex.Observable.subscribe(Observable.java:12090)
56 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
57 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
58 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
59 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
60 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
61 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
62 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
63 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
64 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
65 | at java.lang.Thread.run(Thread.java:748)
66 | Caused by: java.lang.NullPointerException: The mapper function returned a null value.
67 | at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
68 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
69 | ... 14 more
70 | ```
71 |
72 | This is basically impossible to investigate if you're looking at a crash report from the wild.
73 |
74 | Now the same error with RxDogTag enabled:
75 |
76 | ```
77 | io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value.
78 |
79 | Caused by: java.lang.NullPointerException: The mapper function returned a null value.
80 | at anotherpackage.ReadMeExample.complex(ReadMeExample.java:55)
81 | at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
82 | at [[ ↓↓ Original trace ↓↓ ]].(:0)
83 | at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
84 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
85 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
86 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
87 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
88 | at io.reactivex.Observable.subscribe(Observable.java:12090)
89 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
90 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
91 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
92 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
93 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
94 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
95 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
96 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
97 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
98 | at java.lang.Thread.run(Thread.java:748)
99 | ```
100 |
101 | Now we have the example subscribe line at `ReadMeExample.java:55`. It may not be a silver bullet to
102 | root-causing why the exception occurred, but at least you know where it's emanating from.
103 |
104 | The subscribe line reported should also retrace and group well for crash reporting. As we use our own
105 | in-house reporter though, we're very open to feedback on how this can be improved for other solutions.
106 |
107 | More examples and details can be found in the [wiki](https://github.com/uber/RxDogTag/wiki)
108 |
109 | ## Configuration
110 |
111 | RxDogTag has an alternative `RxDogTag.builder()` API to facilitate added configuration, such as annotation
112 | control, stacktrace element location, and more.
113 |
114 | ### Custom handlers
115 |
116 | In the event of custom observers that possibly decorate other observer types, this information can
117 | be passed to RxDogTag via the `ObserverHandler` interface. This interface can be used to unwrap
118 | these custom observers to reveal their delegates and their potential behavior. Install these via
119 | the `RxDogTag.Builder#addObserverHandlers(...)` overloads that accept handlers.
120 |
121 | ### Ignored packages
122 |
123 | RxDogTag needs to ignore certain packages (such as its own or RxJava's) when inspecting stack traces
124 | to deduce the subscribe point. You can add other custom ones via `RxDogTag.Builder#addIgnoredPackages(...)`.
125 |
126 | ### AutoDispose support
127 |
128 | AutoDispose is a library for automatically disposing streams, and works via its own decorating observers
129 | under the hood. AutoDispose can work with RxDogTag via its `delegateObserver()` APIs on the AutoDisposingObserver
130 | interfaces. Support for this is available via separate `rxdogtag-autodispose` artifact and its
131 | `AutoDisposeObserverHandler` singleton instance.
132 |
133 | ```java
134 | RxDogTag.builder()
135 | .configureWith(AutoDisposeConfigurer::configure)
136 | .install();
137 | ```
138 |
139 | If you're targeting RxJava 2:
140 |
141 | [](https://mvnrepository.com/artifact/com.uber.rxdogtag/rxdogtag-autodispose)
142 |
143 | ```gradle
144 | implementation("com.uber.rxdogtag:rxdogtag-autodispose:x.y.z")
145 | ```
146 |
147 | If you're targeting RxJava 3:
148 |
149 | [](https://mvnrepository.com/artifact/com.uber.rxdogtag2/rxdogtag-autodispose)
150 |
151 | ```gradle
152 | implementation("com.uber.rxdogtag2:rxdogtag-autodispose:x.y.z")
153 | ```
154 |
155 | ## Development
156 |
157 | Javadocs for the most recent release can be found here: https://uber.github.io/RxDogTag/0.x/rxdogtag/com.uber.rxdogtag/
158 |
159 | Snapshots of the development version are available in [Sonatype's snapshots repository][snapshots].
160 |
161 | License
162 | -------
163 |
164 | Copyright (C) 2019 Uber Technologies
165 |
166 | Licensed under the Apache License, Version 2.0 (the "License");
167 | you may not use this file except in compliance with the License.
168 | You may obtain a copy of the License at
169 |
170 | http://www.apache.org/licenses/LICENSE-2.0
171 |
172 | Unless required by applicable law or agreed to in writing, software
173 | distributed under the License is distributed on an "AS IS" BASIS,
174 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
175 | See the License for the specific language governing permissions and
176 | limitations under the License.
177 |
178 | [snapshots]: https://oss.sonatype.org/content/repositories/snapshots/com/uber/rxdogtag/
179 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/RxDogTagConfigurationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static com.google.common.truth.Truth.assertThat;
19 |
20 | import io.reactivex.rxjava3.core.Observable;
21 | import io.reactivex.rxjava3.core.Observer;
22 | import io.reactivex.rxjava3.disposables.Disposable;
23 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
24 | import io.reactivex.rxjava3.observers.DisposableObserver;
25 | import io.reactivex.rxjava3.observers.LambdaConsumerIntrospection;
26 | import io.reactivex.rxjava3.plugins.RxJavaPlugins;
27 | import org.junit.After;
28 | import org.junit.Rule;
29 | import org.junit.Test;
30 | import rxdogtag2.ObserverHandler;
31 | import rxdogtag2.RxDogTag;
32 |
33 | public final class RxDogTagConfigurationTest implements DogTagTest {
34 |
35 | @Rule public RxErrorsRule errorsRule = new RxErrorsRule();
36 |
37 | @After
38 | public void tearDown() {
39 | RxDogTag.reset();
40 | }
41 |
42 | @Test
43 | public void installAndResetObservers() {
44 | RxDogTag.install();
45 | assertThat(RxJavaPlugins.getOnFlowableSubscribe()).isNotNull();
46 | assertThat(RxJavaPlugins.getOnObservableSubscribe()).isNotNull();
47 | assertThat(RxJavaPlugins.getOnMaybeSubscribe()).isNotNull();
48 | assertThat(RxJavaPlugins.getOnSingleSubscribe()).isNotNull();
49 | assertThat(RxJavaPlugins.getOnCompletableSubscribe()).isNotNull();
50 |
51 | RxDogTag.reset();
52 | assertThat(RxJavaPlugins.getOnFlowableSubscribe()).isNull();
53 | assertThat(RxJavaPlugins.getOnObservableSubscribe()).isNull();
54 | assertThat(RxJavaPlugins.getOnMaybeSubscribe()).isNull();
55 | assertThat(RxJavaPlugins.getOnSingleSubscribe()).isNull();
56 | assertThat(RxJavaPlugins.getOnCompletableSubscribe()).isNull();
57 | }
58 |
59 | @Test
60 | public void customHandlersGoFirst() {
61 | // A custom handler that hides the upstream observer to demonstrate that custom handlers get
62 | // first pick.
63 | ObserverHandler handler =
64 | new ObserverHandler() {
65 | @Override
66 | public Observer handle(Observable observable, Observer observer) {
67 | return new LambdaConsumerObserver() {
68 | @Override
69 | public void onSubscribe(Disposable d) {
70 | observer.onSubscribe(d);
71 | }
72 |
73 | @Override
74 | public void onNext(Object o) {
75 | //noinspection unchecked
76 | observer.onNext(o);
77 | }
78 |
79 | @Override
80 | public void onError(Throwable e) {
81 | RxJavaPlugins.onError(e);
82 | }
83 |
84 | @Override
85 | public void onComplete() {
86 | observer.onComplete();
87 | }
88 |
89 | @Override
90 | public boolean hasCustomOnError() {
91 | return false;
92 | }
93 | };
94 | }
95 | };
96 |
97 | RxDogTag.builder().addObserverHandlers(handler).install();
98 | Exception expected = new RuntimeException("Exception!");
99 | Observable.error(expected)
100 | .subscribe(
101 | new DisposableObserver() {
102 | @Override
103 | public void onNext(Object o) {}
104 |
105 | @Override
106 | public void onError(Throwable e) {
107 | throw new RuntimeException("Another exception");
108 | }
109 |
110 | @Override
111 | public void onComplete() {}
112 | });
113 |
114 | Throwable e = errorsRule.take();
115 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
116 | assertThat(e.getStackTrace()).isEmpty();
117 | Throwable cause = e.getCause();
118 | assertThat(cause).isSameInstanceAs(expected);
119 | assertThat(cause.getStackTrace()[1].toString()).contains(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
120 | }
121 |
122 | @Test
123 | public void customHandlersAreSkippedPastIfNoHandling() {
124 | // A custom handler that doesn't report a LambdaConsumerIntrospection
125 | ObserverHandler handler =
126 | new ObserverHandler() {
127 | @Override
128 | public Observer handle(Observable observable, Observer observer) {
129 | return observer;
130 | }
131 | };
132 |
133 | RxDogTag.builder().addObserverHandlers(handler).install();
134 | Exception expected = new RuntimeException("Exception!");
135 | Observable.error(expected).subscribe();
136 |
137 | Throwable e = errorsRule.take();
138 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
139 | assertThat(e.getStackTrace()).isEmpty();
140 | Throwable cause = e.getCause();
141 | assertThat(cause).isSameInstanceAs(expected);
142 | assertThat(cause.getStackTrace()[1].toString()).contains(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
143 | }
144 |
145 | @Test
146 | public void customPackages() {
147 | String thisPackage = getClass().getPackage().getName();
148 |
149 | RxDogTag.builder().addIgnoredPackages(thisPackage).install();
150 | Exception expected = new RuntimeException("Exception!");
151 | Observable.error(expected).subscribe();
152 |
153 | Throwable e = errorsRule.take();
154 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
155 | assertThat(e.getStackTrace()).isEmpty();
156 | Throwable cause = e.getCause();
157 | assertThat(cause).isSameInstanceAs(expected);
158 | assertThat(cause.getStackTrace()[1].toString()).contains(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
159 |
160 | // Confirm that we ignored the subscribe line in this test because of the matching package.
161 | assertThat(cause.getStackTrace()[1].toString().startsWith(thisPackage)).isFalse();
162 | }
163 |
164 | @Test
165 | public void disableAnnotations() {
166 | RxDogTag.builder().disableAnnotations().install();
167 | Exception expected = new RuntimeException("Exception!");
168 | Observable.error(expected).subscribe();
169 |
170 | Throwable e = errorsRule.take();
171 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
172 | assertThat(e.getStackTrace()).isEmpty();
173 | Throwable cause = e.getCause();
174 | assertThat(cause).isSameInstanceAs(expected);
175 | assertThat(cause.getStackTrace()[1].toString())
176 | .doesNotContain(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
177 | }
178 |
179 | @Test
180 | public void repackagingOENIEs_onByDefault() {
181 | RxDogTag.builder().install();
182 | Exception expected = new RuntimeException("Exception!");
183 | OnErrorNotImplementedException parent = new OnErrorNotImplementedException(expected);
184 | Observable.error(parent).subscribe();
185 |
186 | Throwable e = errorsRule.take();
187 |
188 | // Confirm it's repackaged
189 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
190 | assertThat(e.getStackTrace()).isEmpty();
191 | assertThat(e).hasMessageThat().isEqualTo(expected.getMessage());
192 | Throwable cause = e.getCause();
193 | assertThat(cause).isSameInstanceAs(expected);
194 | assertThat(cause.getStackTrace()[1].toString()).contains(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
195 | }
196 |
197 | @Test
198 | public void repackagingOENIEs_disabled() {
199 | RxDogTag.builder().disableRepackagingOnErrorNotImplementedExceptions().install();
200 | Exception expected = new RuntimeException("Exception!");
201 | OnErrorNotImplementedException parent = new OnErrorNotImplementedException(expected);
202 | Observable.error(parent).subscribe();
203 |
204 | Throwable e = errorsRule.take();
205 |
206 | // Assert it's the original, untouched exception
207 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
208 | assertThat(e).isSameInstanceAs(parent);
209 | assertThat(e.getStackTrace()).isEqualTo(parent.getStackTrace());
210 | assertThat(e).hasMessageThat().isEqualTo(parent.getMessage());
211 | Throwable cause = e.getCause();
212 | assertThat(cause).isSameInstanceAs(expected);
213 | assertThat(cause.getStackTrace()[1].toString()).contains(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
214 | }
215 |
216 | @Test
217 | public void disableGuardObserverChecks_rewritesStacktrace() {
218 | RxDogTag.builder().guardObserverCallbacks(false).install();
219 |
220 | Exception original = new RuntimeException("Exception!");
221 | Observable.error(original).subscribe();
222 |
223 | Throwable e = errorsRule.take();
224 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
225 | assertThat(e).hasMessageThat().isEqualTo(original.getMessage());
226 | assertThat(e.getStackTrace()).isEmpty();
227 | Throwable cause = e.getCause();
228 | assertThat(cause.getStackTrace()[0].getFileName())
229 | .isEqualTo(getClass().getSimpleName() + ".java");
230 | assertThat(cause.getStackTrace()[1].getClassName())
231 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
232 | assertThat(cause.getStackTrace()[2].getClassName())
233 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
234 | }
235 |
236 | abstract static class LambdaConsumerObserver
237 | implements Observer, LambdaConsumerIntrospection {}
238 | }
239 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/android-benchmark/benchmark/src/main/kotlin/rxdogtag2/androidbenchmark/DataParser.kt:
--------------------------------------------------------------------------------
1 | package rxdogtag2.androidbenchmark
2 |
3 | import java.util.Locale
4 |
5 | fun main() {
6 |
7 | val data = """
8 | benchmark: 9,770 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=0,guardedDelegateEnabled=false]
9 | benchmark: 20,783 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=0,guardedDelegateEnabled=false]
10 | benchmark: 7,506 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=0,guardedDelegateEnabled=false]
11 | benchmark: 7,275 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=0,guardedDelegateEnabled=false]
12 | benchmark: 50,492 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,guardedDelegateEnabled=true]
13 | benchmark: 50,787 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,guardedDelegateEnabled=true]
14 | benchmark: 7,581 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,guardedDelegateEnabled=true]
15 | benchmark: 7,378 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,guardedDelegateEnabled=true]
16 | benchmark: 49,195 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,guardedDelegateEnabled=false]
17 | benchmark: 50,081 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,guardedDelegateEnabled=false]
18 | benchmark: 7,234 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,guardedDelegateEnabled=false]
19 | benchmark: 7,060 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,guardedDelegateEnabled=false]
20 | benchmark: 313,334 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,000,guardedDelegateEnabled=true]
21 | benchmark: 342,812 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,000,guardedDelegateEnabled=true]
22 | benchmark: 153,124 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,000,guardedDelegateEnabled=true]
23 | benchmark: 152,943 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,000,guardedDelegateEnabled=true]
24 | benchmark: 312,864 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,000,guardedDelegateEnabled=false]
25 | benchmark: 375,208 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,000,guardedDelegateEnabled=false]
26 | benchmark: 40,568 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,000,guardedDelegateEnabled=false]
27 | benchmark: 40,743 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,000,guardedDelegateEnabled=false]
28 | benchmark: 257,553,671 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,000,000,guardedDelegateEnabled=true]
29 | benchmark: 300,186,228 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,000,000,guardedDelegateEnabled=true]
30 | benchmark: 162,547,359 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,000,000,guardedDelegateEnabled=true]
31 | benchmark: 166,887,047 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,000,000,guardedDelegateEnabled=true]
32 | benchmark: 249,739,764 ns RxDogTagAndroidPerf.observable_complex[enabled=true,times=1,000,000,guardedDelegateEnabled=false]
33 | benchmark: 302,880,498 ns RxDogTagAndroidPerf.flowable_complex[enabled=true,times=1,000,000,guardedDelegateEnabled=false]
34 | benchmark: 26,791,825 ns RxDogTagAndroidPerf.flowable_simple[enabled=true,times=1,000,000,guardedDelegateEnabled=false]
35 | benchmark: 27,304,847 ns RxDogTagAndroidPerf.observable_simple[enabled=true,times=1,000,000,guardedDelegateEnabled=false]
36 | benchmark: 1,673 ns RxDogTagAndroidPerf.observable_complex[enabled=false,times=0,guardedDelegateEnabled=false]
37 | benchmark: 5,741 ns RxDogTagAndroidPerf.flowable_complex[enabled=false,times=0,guardedDelegateEnabled=false]
38 | benchmark: 365 ns RxDogTagAndroidPerf.flowable_simple[enabled=false,times=0,guardedDelegateEnabled=false]
39 | benchmark: 331 ns RxDogTagAndroidPerf.observable_simple[enabled=false,times=0,guardedDelegateEnabled=false]
40 | benchmark: 53,672 ns RxDogTagAndroidPerf.observable_complex[enabled=false,times=1,guardedDelegateEnabled=false]
41 | benchmark: 56,004 ns RxDogTagAndroidPerf.flowable_complex[enabled=false,times=1,guardedDelegateEnabled=false]
42 | benchmark: 519 ns RxDogTagAndroidPerf.flowable_simple[enabled=false,times=1,guardedDelegateEnabled=false]
43 | benchmark: 532 ns RxDogTagAndroidPerf.observable_simple[enabled=false,times=1,guardedDelegateEnabled=false]
44 | benchmark: 291,301 ns RxDogTagAndroidPerf.observable_complex[enabled=false,times=1,000,guardedDelegateEnabled=false]
45 | benchmark: 273,178 ns RxDogTagAndroidPerf.flowable_complex[enabled=false,times=1,000,guardedDelegateEnabled=false]
46 | benchmark: 24,920 ns RxDogTagAndroidPerf.flowable_simple[enabled=false,times=1,000,guardedDelegateEnabled=false]
47 | benchmark: 23,968 ns RxDogTagAndroidPerf.observable_simple[enabled=false,times=1,000,guardedDelegateEnabled=false]
48 | benchmark: 252,727,577 ns RxDogTagAndroidPerf.observable_complex[enabled=false,times=1,000,000,guardedDelegateEnabled=false]
49 | benchmark: 304,952,062 ns RxDogTagAndroidPerf.flowable_complex[enabled=false,times=1,000,000,guardedDelegateEnabled=false]
50 | benchmark: 23,952,919 ns RxDogTagAndroidPerf.flowable_simple[enabled=false,times=1,000,000,guardedDelegateEnabled=false]
51 | benchmark: 23,993,700 ns RxDogTagAndroidPerf.observable_simple[enabled=false,times=1,000,000,guardedDelegateEnabled=false]
52 | """.trimIndent()
53 |
54 | // Skip the header line
55 | val results = data.lineSequence()
56 | .filterNot { it.isBlank() }
57 | .filter { it.startsWith("benchmark") }
58 | .map { line ->
59 | // benchmark: 6,154,949 ns SpeedTest.gson_autovalue_buffer_fromJson_minified
60 | val (_, score, units, benchmark) = line.split("\\s+".toRegex())
61 | Analysis(
62 | benchmark = benchmark,
63 | score = score.replace(",", "").toLong(),
64 | units = units
65 | )
66 | }
67 | .toList()
68 |
69 | ResultType.values().forEach { printResults(it, results) }
70 | }
71 |
72 | private fun printResults(type: ResultType, results: List) {
73 | val groupedResults = type.groupings.associateWith { grouping ->
74 | results.filter {
75 | grouping.matchFunction(it.benchmark)
76 | }
77 | }
78 | check(groupedResults.isNotEmpty()) {
79 | "Empty results for $type with \n$results"
80 | }
81 | val scoreLength = results.maxBy { it.formattedScore.length }!!.formattedScore.length
82 |
83 | val output = buildString {
84 | appendln()
85 | append("## ")
86 | append(type.title)
87 | appendln()
88 | appendln()
89 | append("_")
90 | append(type.description)
91 | append("_")
92 | appendln()
93 | appendln()
94 | groupedResults.entries
95 | .joinTo(this, "\n\n", postfix = "\n") { (grouping, matchedAnalyses) ->
96 | check(matchedAnalyses.isNotEmpty()) {
97 | "Empty analysis for $type, $grouping, with \n$matchedAnalyses"
98 | }
99 | val sorted = matchedAnalyses.sortedBy { it.score }
100 | val first = sorted[0]
101 | val largestDelta = sorted.drop(1)
102 | .map {
103 | val delta = ((it.score - first.score).toDouble() / first.score) * 100
104 | String.format(Locale.US, "%.2f", delta)
105 | }
106 | .maxBy {
107 | it.length
108 | }!!
109 | .length
110 | val content = sorted
111 | .joinToString("\n") { analysis ->
112 | analysis.formattedString(
113 | scoreLength,
114 | largestDelta
115 | )
116 | }
117 | "### ${grouping.name}" +
118 | "\n| RxDogTag Enabled | Guarded Observer Callbacks Enabled | Time (ms) | Time (ns) |" +
119 | "\n|----------|----------|------------|-----------|" +
120 | "\n$content"
121 | }
122 | }
123 |
124 | println(output)
125 | }
126 |
127 | private fun String.isFlowable(): Boolean = "flowable" in this
128 | private fun String.isObservable(): Boolean = "observable" in this
129 | private fun String.isSubscribeThroughput(): Boolean = "times=0" in this
130 | private fun String.isSingle(): Boolean = "=1,g" in this
131 | private fun String.isThousand(): Boolean = "=1,000,g" in this
132 | private fun String.isMillion(): Boolean = "=1,000,000,g" in this
133 | private fun String.isSimple(): Boolean = "simple" in this
134 | private fun String.isComplex(): Boolean = "complex" in this
135 |
136 | private enum class ResultType(val title: String, val description: String, val groupings: List) {
137 | THROUGHPUT(
138 | title = "Event throughput: grouped by number of events",
139 | description = "Measures the amount of time it takes for given number of elements to pass through the stream.",
140 | groupings = run {
141 | mutableListOf().apply {
142 | val complexities: Map Boolean> = mapOf(
143 | "Simple" to String::isSimple,
144 | "Complex" to String::isComplex
145 | )
146 | val counts: Map Boolean> = mapOf(
147 | "1 item" to String::isSingle,
148 | "1_000 items" to String::isThousand,
149 | "1_000_000 items" to String::isMillion
150 | )
151 | val types: Map Boolean> = mapOf(
152 | "Observable" to String::isObservable,
153 | "Flowable" to String::isFlowable
154 | )
155 | for (type in types) {
156 | for (count in counts) {
157 | for (complexity in complexities) {
158 | add(Grouping("${complexity.key}: ${count.key} (${type.key})") {
159 | complexity.value.invoke(it) && count.value.invoke(it) && type.value.invoke(it)
160 | })
161 | }
162 | }
163 | }
164 | }
165 | }
166 | ),
167 | SUBSCRIBE(
168 | title = "Subscribe cost: grouped by complexity",
169 | description = "Measures the cost to subscription incurred by RxDogTag. Subscription means no emissions, subscription only.",
170 | groupings = listOf(
171 | Grouping("Simple (Observable)") {
172 | it.isSimple() && it.isSubscribeThroughput() && it.isObservable()
173 | },
174 | Grouping("Simple (Flowable)") {
175 | it.isSimple() && it.isSubscribeThroughput() && it.isFlowable()
176 | },
177 | Grouping("Complex (Observable)") {
178 | it.isComplex() && it.isSubscribeThroughput() && it.isObservable()
179 | },
180 | Grouping("Complex (Flowable)") {
181 | it.isComplex() && it.isSubscribeThroughput() && it.isFlowable()
182 | }
183 | )
184 | )
185 | }
186 |
187 | private data class Grouping(
188 | val name: String,
189 | val matchFunction: (String) -> Boolean
190 | )
191 |
192 | private data class Analysis(
193 | val benchmark: String,
194 | val score: Long,
195 | val units: String
196 | ) {
197 | override fun toString() = "$benchmark\t$score\t$units"
198 |
199 | val rxDogTagEnabled = benchmark.substringAfter("enabled=")
200 | .substringBefore(",")
201 | .toBoolean()
202 | val guardedObserverCallbacksEnabled = benchmark.substringAfter("guardedDelegateEnabled=")
203 | .substringBefore("]")
204 | .toBoolean()
205 |
206 | fun formattedString(scoreLength: Int, msLength: Int): String {
207 | return String.format(Locale.US,
208 | "| %-${5}s | %-${5}s | %$msLength.3f%s | %${scoreLength}s%s |",
209 | rxDogTagEnabled,
210 | guardedObserverCallbacksEnabled,
211 | score.toFloat() / 1000000,
212 | "ms",
213 | formattedScore,
214 | units
215 | )
216 | }
217 |
218 | val formattedScore: String
219 | get() = String.format(Locale.US, "%,d", score)
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/docs/examples.md:
--------------------------------------------------------------------------------
1 | ## Examples
2 |
3 | ### Simple
4 |
5 | Consider the following trivial case of emitting an error with
6 | no error handling.
7 |
8 | ```java
9 | Observable.error(new RuntimeException("Unhandled error!"))
10 | .subscribe();
11 | ```
12 |
13 | This results in a stacktrace like this:
14 |
15 | ```jvm
16 | io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | Unhandled error!
17 |
18 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
19 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
20 | at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
21 | at io.reactivex.internal.disposables.EmptyDisposable.error(EmptyDisposable.java:63)
22 | at io.reactivex.internal.operators.observable.ObservableError.subscribeActual(ObservableError.java:38)
23 | at io.reactivex.Observable.subscribe(Observable.java:12090)
24 | at io.reactivex.Observable.subscribe(Observable.java:12076)
25 | at io.reactivex.Observable.subscribe(Observable.java:11954)
26 | at anotherpackage.ReadMeExample.simpleSubscribe(ReadMeExample.java:26)
27 |
28 | Caused by: java.lang.RuntimeException: Unhandled error!
29 | at anotherpackage.ReadMeExample.simpleSubscribe(ReadMeExample.java:25)
30 | ... 25 more
31 | ```
32 |
33 | Now let's look at the same example with tagging enabled:
34 |
35 | ```
36 | io.reactivex.exceptions.OnErrorNotImplementedException: Unhandled error!
37 |
38 | Caused by: java.lang.RuntimeException: Unhandled error!
39 | at anotherpackage.ReadMeExample.simpleSubscribe(ReadMeExample.java:26)
40 | at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
41 | at [[ ↓↓ Original trace ↓↓ ]].(:0)
42 | at anotherpackage.ReadMeExample.simpleSubscribe(ReadMeExample.java:25)
43 | ... 25 more
44 | ```
45 |
46 | In the simple case, there's not much added benefit since the execution was synchronous and single threaded,
47 | but you can see original trace has now been updated to indicate the exact line that `subscribe()` was called.
48 | In this case: `ReadMeExample.java:26`.
49 |
50 | To reduce noise - RxDogTag will wrap the original cause in a synthetic
51 | `OnErrorNotImplementedException`. This uses the original cause's message and doesn't fill in the
52 | stacktrace as it's irrelevant to the trace.
53 |
54 | ### Threading
55 |
56 | Consider a more complex example with threading:
57 |
58 | ```java
59 | Observable.just(1)
60 | .subscribeOn(Schedulers.io())
61 | .map(i -> null)
62 | .subscribe();
63 | ```
64 |
65 | This is a fairly common case in RxJava concurrency. Without tagging, this yields the following trace:
66 |
67 | ```
68 | io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | The mapper function returned a null value.
69 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
70 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
71 | at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
72 | at io.reactivex.internal.observers.BasicFuseableObserver.onError(BasicFuseableObserver.java:100)
73 | at io.reactivex.internal.observers.BasicFuseableObserver.fail(BasicFuseableObserver.java:110)
74 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59)
75 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
76 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
77 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
78 | at io.reactivex.Observable.subscribe(Observable.java:12090)
79 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
80 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
81 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
82 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
83 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
84 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
85 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
86 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
87 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
88 | at java.lang.Thread.run(Thread.java:748)
89 | Caused by: java.lang.NullPointerException: The mapper function returned a null value.
90 | at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
91 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
92 | ... 14 more
93 | ```
94 |
95 | Yikes! This is basically impossible to investigate if you're looking at a crash report from the wild.
96 |
97 | Now the same trace with tagging enabled:
98 |
99 | ```
100 | io.reactivex.exceptions.OnErrorNotImplementedException: The mapper function returned a null value.
101 |
102 | Caused by: java.lang.NullPointerException: The mapper function returned a null value.
103 | at anotherpackage.ReadMeExample.complex(ReadMeExample.java:55)
104 | at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
105 | at [[ ↓↓ Original trace ↓↓ ]].(:0)
106 | at io.reactivex.internal.functions.ObjectHelper.requireNonNull(ObjectHelper.java:39)
107 | at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:57)
108 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
109 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
110 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
111 | at io.reactivex.Observable.subscribe(Observable.java:12090)
112 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
113 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
114 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
115 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
116 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
117 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
118 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
119 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
120 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
121 | at java.lang.Thread.run(Thread.java:748)
122 | ```
123 |
124 | Now we have the example subscribe line at `ReadMeExample.java:55`. It may not be a silver bullet to
125 | root-causing why the exception occurred, but at least you know where it's emanating from.
126 |
127 | ### Delegate callbacks
128 |
129 | Let's look at one more example. This is similar to the previous, but instead of the exception occurring
130 | upstream in the chain, the exception occurs in one of the observer callbacks; in this case - `onNext`.
131 |
132 | ```java
133 | Observable.just(1)
134 | .subscribeOn(Schedulers.io())
135 | .subscribe(i -> throwSomething());
136 |
137 | private void throwSomething() {
138 | throw new RuntimeException("Unhandled error!");
139 | }
140 | ```
141 |
142 | Without tagging, this yields the following trace:
143 |
144 | ```
145 | io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | Unhandled error!
146 |
147 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
148 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
149 | at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
150 | at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:67)
151 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
152 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
153 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
154 | at io.reactivex.Observable.subscribe(Observable.java:12090)
155 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
156 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
157 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
158 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
159 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
160 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
161 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
162 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
163 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
164 | at java.lang.Thread.run(Thread.java:748)
165 | Caused by: java.lang.RuntimeException: Unhandled error!
166 | at anotherpackage.ReadMeExample.throwSomething(ReadMeExample.java:68)
167 | at anotherpackage.ReadMeExample.lambda$complexDelegate$1(ReadMeExample.java:63)
168 | at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:63)
169 | ... 14 more
170 | ```
171 |
172 | Similar to the first example, this isn't terrible to root-cause. `onNext` is throwing a traceable
173 | exception in this trivial case.
174 |
175 | With tagging enabled:
176 |
177 | ```
178 | io.reactivex.exceptions.OnErrorNotImplementedException: The exception was not handled due to missing onError handler in the subscribe() method call. Further reading: https://github.com/ReactiveX/RxJava/wiki/Error-Handling | Unhandled error!
179 |
180 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:704)
181 | at io.reactivex.internal.functions.Functions$OnErrorMissingConsumer.accept(Functions.java:701)
182 | at io.reactivex.internal.observers.LambdaObserver.onError(LambdaObserver.java:77)
183 | at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:67)
184 | at rxdogtag2.DogTagObserver.lambda$onNext$3(DogTagObserver.java:53)
185 | at rxdogtag2.RxDogTag.guardedDelegateCall(RxDogTag.java:262)
186 | at rxdogtag2.DogTagObserver.onNext(DogTagObserver.java:53)
187 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeOnObserver.onNext(ObservableSubscribeOn.java:58)
188 | at io.reactivex.internal.operators.observable.ObservableScalarXMap$ScalarDisposable.run(ObservableScalarXMap.java:248)
189 | at io.reactivex.internal.operators.observable.ObservableJust.subscribeActual(ObservableJust.java:35)
190 | at io.reactivex.Observable.subscribe(Observable.java:12090)
191 | at io.reactivex.internal.operators.observable.ObservableSubscribeOn$SubscribeTask.run(ObservableSubscribeOn.java:96)
192 | at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
193 | at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
194 | at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
195 | at java.util.concurrent.FutureTask.run(FutureTask.java:266)
196 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
197 | at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
198 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
199 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
200 | at java.lang.Thread.run(Thread.java:748)
201 | Caused by: java.lang.RuntimeException: Unhandled error!
202 | at anotherpackage.ReadMeExample.complexDelegate(ReadMeExample.java:63)
203 | at [[ ↑↑ Inferred subscribe point ↑↑ ]].(:0)
204 | at [[ Originating callback: onNext ]].(:0)
205 | at [[ ↓↓ Original trace ↓↓ ]].(:0)
206 | at anotherpackage.ReadMeExample.throwSomething(ReadMeExample.java:68)
207 | at anotherpackage.ReadMeExample.lambda$complexDelegate$1(ReadMeExample.java:63)
208 | at io.reactivex.internal.observers.LambdaObserver.onNext(LambdaObserver.java:63)
209 | ... 17 more
210 | ```
211 |
212 | Now we're given added context that this occurred in the `onNext` callback according to `[[ Originating callback: onNext ]]`
213 | at the `subscribe()` call at `ReadMeExample.java:63`. This callback handling is supported for all the
214 | standard callbacks and will report the correct name for each (`onSuccess`, `onComplete`, etc).
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/DogTagObserverTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static anotherpackage.DogTagTestUtil.getPreviousLineNumber;
19 | import static com.google.common.truth.Truth.assertThat;
20 | import static org.junit.Assert.fail;
21 |
22 | import io.reactivex.rxjava3.core.Completable;
23 | import io.reactivex.rxjava3.core.Flowable;
24 | import io.reactivex.rxjava3.core.Maybe;
25 | import io.reactivex.rxjava3.core.Observable;
26 | import io.reactivex.rxjava3.core.Observer;
27 | import io.reactivex.rxjava3.core.Single;
28 | import io.reactivex.rxjava3.disposables.Disposable;
29 | import io.reactivex.rxjava3.exceptions.CompositeException;
30 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
31 | import io.reactivex.rxjava3.observers.TestObserver;
32 | import io.reactivex.rxjava3.subjects.PublishSubject;
33 | import java.util.Locale;
34 | import java.util.concurrent.atomic.AtomicReference;
35 | import org.junit.After;
36 | import org.junit.Before;
37 | import org.junit.Rule;
38 | import org.junit.Test;
39 | import rxdogtag2.RxDogTag;
40 | import rxdogtag2.RxDogTagErrorReceiver;
41 | import rxdogtag2.RxDogTagTaggedExceptionReceiver;
42 |
43 | /**
44 | * NOTE: These tests are a little odd. There are two conditions for them running correctly because
45 | * they verify via inspecting stacktraces. 1. the throwError() method MUST calculate the line number
46 | * immediately after subscribe (on the next line). 2. it must not be in the rxdogtag2 package
47 | * because that is filtered out in stacktrace inspection.
48 | */
49 | public class DogTagObserverTest implements DogTagTest {
50 |
51 | @Rule public RxErrorsRule errorsRule = new RxErrorsRule();
52 |
53 | @Before
54 | public void setUp() {
55 | RxDogTag.install();
56 | }
57 |
58 | @After
59 | public void tearDown() {
60 | RxDogTag.reset();
61 | }
62 |
63 | @Test
64 | public void testObservable() {
65 | Exception original = new RuntimeException("Blah");
66 | assertRewrittenStacktrace(subscribeError(Observable.error(original)), original);
67 | }
68 |
69 | @Test
70 | public void testFlowable() {
71 | Exception original = new RuntimeException("Blah");
72 | assertRewrittenStacktrace(subscribeError(Flowable.error(original)), original);
73 | }
74 |
75 | @Test
76 | public void testSingle() {
77 | Exception original = new RuntimeException("Blah");
78 | assertRewrittenStacktrace(subscribeError(Single.error(original)), original);
79 | }
80 |
81 | @Test
82 | public void testMaybe() {
83 | Exception original = new RuntimeException("Blah");
84 | assertRewrittenStacktrace(subscribeError(Maybe.error(original)), original);
85 | }
86 |
87 | @Test
88 | public void testCompletable() {
89 | Exception original = new RuntimeException("Blah");
90 | assertRewrittenStacktrace(subscribeError(Completable.error(original)), original);
91 | }
92 |
93 | @Test
94 | public void noConstructor_generatesStringFromStackElement_anonymous() {
95 | Exception originalError = new IllegalStateException("illegal state exception");
96 | assertRewrittenStacktrace(
97 | throwError(new EmptyObserver() {}, originalError), originalError);
98 | }
99 |
100 | @Test
101 | public void noConstructor_generatesStringFromStackElement_instance() {
102 | Exception originalError = new IllegalStateException("illegal state exception");
103 | Observer o = new EmptyObserver() {};
104 | assertRewrittenStacktrace(throwError(o, originalError), originalError);
105 | }
106 |
107 | @Test
108 | public void noConstructor_generatesStringFromStackElement_subclass() {
109 | Exception originalError = new IllegalStateException("illegal state exception");
110 | Another o = new Another();
111 | assertRewrittenStacktrace(throwError(o, originalError), originalError);
112 | }
113 |
114 | @Test
115 | public void observerWithErrorHandling_ignoredByRxDogTag() {
116 | Exception originalError = new IllegalStateException("illegal state exception");
117 | // Test observer which custom error handling.
118 | TestObserver testObserver = Observable.error(originalError).test();
119 | // No errors intercepted by RxDogTag.
120 | errorsRule.assertNoErrors();
121 |
122 | testObserver.assertError(
123 | (error) -> {
124 | assertThat(error).isNotInstanceOf(OnErrorNotImplementedException.class);
125 | assertThat(error).hasMessageThat().isEqualTo(originalError.getMessage());
126 | assertThat(error.getStackTrace()).isNotEmpty();
127 | return true;
128 | });
129 | }
130 |
131 | @Test
132 | public void nullOriginErrorMessage_replacedWithEmptyString() {
133 | Exception originalError = new IllegalStateException();
134 | int expectedLineNumber = subscribeError(Observable.error(originalError));
135 |
136 | Throwable e = errorsRule.take();
137 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
138 | // Original error message was null. Now replaced by empty string
139 | assertThat(e).hasMessageThat().isNotEqualTo(originalError.getMessage());
140 | assertThat(e).hasMessageThat().isEqualTo("");
141 |
142 | assertThat(e.getStackTrace()).isEmpty();
143 | Throwable cause = e.getCause();
144 | assertThat(cause.getStackTrace()[0].getFileName())
145 | .isEqualTo(getClass().getSimpleName() + ".java");
146 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(expectedLineNumber);
147 | assertThat(cause.getStackTrace()[1].getClassName())
148 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
149 | assertThat(cause.getStackTrace()[2].getClassName())
150 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
151 | }
152 |
153 | @Test
154 | public void compositeException() {
155 | IllegalStateException original = new IllegalStateException("Initial exception");
156 | RuntimeException runtimeException = new RuntimeException("Resulting exception");
157 |
158 | CompositeException compositeException = new CompositeException(original, runtimeException);
159 | int expectedLineNumber = subscribeError(Observable.error(compositeException));
160 |
161 | assertRewrittenStacktrace(expectedLineNumber, compositeException);
162 | }
163 |
164 | @Test
165 | public void mangledCompositeException() {
166 | Observable mainObservable =
167 | Observable.concatDelayError(
168 | withError(
169 | Observable.just(
170 | withError(
171 | withError(Observable.just(1), new RuntimeException("1")),
172 | new RuntimeException("2"))),
173 | new RuntimeException("3")));
174 |
175 | int expectedLineNumber = subscribeError(mainObservable);
176 | Throwable e = errorsRule.take();
177 | assertThat(e).isInstanceOf(OnErrorNotImplementedException.class);
178 | assertThat(e).hasMessageThat().isEqualTo("2 exceptions occurred. "); // For composite exception
179 | assertThat(e.getStackTrace()).isEmpty();
180 | Throwable cause = e.getCause();
181 | assertThat(cause.getStackTrace()[0].getFileName())
182 | .isEqualTo(getClass().getSimpleName() + ".java");
183 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(expectedLineNumber);
184 | assertThat(cause.getStackTrace()[1].getClassName())
185 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
186 | assertThat(cause.getStackTrace()[2].getClassName())
187 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
188 | }
189 |
190 | @Test
191 | public void errorReceiver_noGuards() {
192 | RxDogTag.reset();
193 | RxDogTag.builder().guardObserverCallbacks(false).install();
194 | AtomicReference recordedError = new AtomicReference<>();
195 | TestErrorReceiver o =
196 | new TestErrorReceiver() {
197 | @Override
198 | public void onError(Throwable e) {
199 | recordedError.compareAndSet(null, e);
200 | }
201 |
202 | @Override
203 | public void onSubscribe(Disposable d) {}
204 |
205 | @Override
206 | public void onNext(Integer integer) {}
207 |
208 | @Override
209 | public void onComplete() {}
210 | };
211 | Exception original = new RuntimeException("Blah");
212 | Observable.error(original).subscribe(o);
213 | Throwable recorded = recordedError.get();
214 | assertThat(recorded).isSameInstanceAs(original);
215 | }
216 |
217 | @Test
218 | public void errorReceiver_withGuard_noException() {
219 | RxDogTag.reset();
220 | RxDogTag.builder().guardObserverCallbacks(true).install();
221 | AtomicReference recordedError = new AtomicReference<>();
222 | TestErrorReceiver o =
223 | new TestErrorReceiver() {
224 | @Override
225 | public void onError(Throwable e) {
226 | recordedError.compareAndSet(null, e);
227 | }
228 |
229 | @Override
230 | public void onSubscribe(Disposable d) {}
231 |
232 | @Override
233 | public void onNext(Integer integer) {}
234 |
235 | @Override
236 | public void onComplete() {}
237 | };
238 | Exception original = new RuntimeException("Blah");
239 | Observable.error(original).subscribe(o);
240 | Throwable recorded = recordedError.get();
241 | assertThat(recorded).isSameInstanceAs(original);
242 | }
243 |
244 | @Test
245 | public void errorReceiver_withGuard_withException() {
246 | RxDogTag.reset();
247 | RxDogTag.builder().guardObserverCallbacks(true).install();
248 | AtomicReference thrownException = new AtomicReference<>();
249 | TestErrorReceiver o =
250 | new TestErrorReceiver() {
251 | @Override
252 | public void onError(Throwable e) {
253 | RuntimeException toThrow = new RuntimeException(e);
254 | thrownException.compareAndSet(null, toThrow);
255 | throw toThrow;
256 | }
257 |
258 | @Override
259 | public void onSubscribe(Disposable d) {}
260 |
261 | @Override
262 | public void onNext(Integer integer) {}
263 |
264 | @Override
265 | public void onComplete() {}
266 | };
267 | Exception original = new RuntimeException("Blah");
268 | PublishSubject subject = PublishSubject.create();
269 | // Can't do this one synchronously because it will fail in subscribe();
270 | subject.subscribe(o);
271 | int lineNumber = getPreviousLineNumber();
272 | subject.onError(original);
273 | Throwable recorded = errorsRule.take();
274 | Throwable thrown = thrownException.get();
275 | if (thrown == null) {
276 | // Partially more descriptive error message, partially to make NullAway happy since it doesn't
277 | // speak truth's isNotNull() assertions
278 | fail("thrown was null! This means the observer's onError was never called");
279 | return;
280 | }
281 | assertThat(thrown).hasCauseThat().isSameInstanceAs(original);
282 | assertThat(recorded).isInstanceOf(OnErrorNotImplementedException.class);
283 | assertThat(recorded).hasCauseThat().isSameInstanceAs(thrown);
284 | // The original cause was put in this
285 | assertThat(recorded).hasMessageThat().contains("java.lang.RuntimeException: Blah");
286 |
287 | // Slightly duplicated, but this is a special delegates case for onError
288 | Throwable cause = recorded.getCause();
289 | assertThat(cause).isNotNull();
290 | assertThat(cause.getStackTrace()[0].getFileName())
291 | .isEqualTo(getClass().getSimpleName() + ".java");
292 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(lineNumber);
293 | assertThat(cause.getStackTrace()[1].getClassName())
294 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
295 | assertThat(cause.getStackTrace()[2].getClassName())
296 | .isEqualTo(String.format(Locale.US, RxDogTag.STACK_ELEMENT_SOURCE_DELEGATE, "onError"));
297 | assertThat(cause.getStackTrace()[3].getClassName())
298 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
299 | }
300 |
301 | @Test
302 | public void taggedErrorReceiver() {
303 | AtomicReference recordedError = new AtomicReference<>();
304 | TestTaggedErrorReceiver o =
305 | new TestTaggedErrorReceiver() {
306 | @Override
307 | public void onError(Throwable e) {
308 | recordedError.compareAndSet(null, e);
309 | }
310 |
311 | @Override
312 | public void onSubscribe(Disposable d) {}
313 |
314 | @Override
315 | public void onNext(Integer integer) {}
316 |
317 | @Override
318 | public void onComplete() {}
319 | };
320 | Exception original = new RuntimeException("Blah");
321 | Observable.error(original).subscribe(o);
322 | int lineNumber = getPreviousLineNumber();
323 | Throwable recorded = recordedError.get();
324 | if (recorded == null) {
325 | fail("No exception recorded!");
326 | } else {
327 | assertRewrittenStacktrace(lineNumber, original, recorded);
328 | }
329 | }
330 |
331 | private interface TestTaggedErrorReceiver
332 | extends RxDogTagTaggedExceptionReceiver, Observer {}
333 |
334 | private interface TestErrorReceiver extends RxDogTagErrorReceiver, Observer {}
335 |
336 | /** This tests that the original stacktrace was rewritten with the relevant source information. */
337 | private void assertRewrittenStacktrace(int expectedLineNumber, Throwable original) {
338 | Throwable e = errorsRule.take();
339 | assertRewrittenStacktrace(expectedLineNumber, original, e);
340 | }
341 | /** This tests that the original stacktrace was rewritten with the relevant source information. */
342 | private void assertRewrittenStacktrace(
343 | int expectedLineNumber, Throwable original, Throwable recorded) {
344 | assertThat(recorded).isInstanceOf(OnErrorNotImplementedException.class);
345 | assertThat(recorded).hasMessageThat().isEqualTo(original.getMessage());
346 | assertThat(recorded.getStackTrace()).isEmpty();
347 | Throwable cause = recorded.getCause();
348 | assertThat(cause.getStackTrace()[0].getFileName())
349 | .isEqualTo(getClass().getSimpleName() + ".java");
350 | assertThat(cause.getStackTrace()[0].getLineNumber()).isEqualTo(expectedLineNumber);
351 | assertThat(cause.getStackTrace()[1].getClassName())
352 | .isEqualTo(RxDogTag.STACK_ELEMENT_SOURCE_HEADER);
353 | assertThat(cause.getStackTrace()[2].getClassName())
354 | .isEqualTo(RxDogTag.STACK_ELEMENT_TRACE_HEADER);
355 | }
356 |
357 | private static int subscribeError(Completable completable) {
358 | completable.subscribe();
359 | return getPreviousLineNumber();
360 | }
361 |
362 | private static int subscribeError(Observable observable) {
363 | observable.subscribe();
364 | return getPreviousLineNumber();
365 | }
366 |
367 | private static int subscribeError(Maybe maybe) {
368 | maybe.subscribe();
369 | return getPreviousLineNumber();
370 | }
371 |
372 | private static int subscribeError(Single single) {
373 | single.subscribe();
374 | return getPreviousLineNumber();
375 | }
376 |
377 | private static int subscribeError(Flowable flowable) {
378 | flowable.subscribe();
379 | return getPreviousLineNumber();
380 | }
381 |
382 | private static int throwError(Observer observer, Exception error) {
383 | Observable.defer(() -> Observable.error(error)).subscribe(observer);
384 | return getPreviousLineNumber();
385 | }
386 |
387 | private static Observable withError(Observable source, Exception exception) {
388 | return source.concatWith(Observable.error(exception));
389 | }
390 |
391 | private static class Another extends EmptyObserver {
392 |
393 | @Override
394 | public void onNext(Object unit) {}
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/rxdogtag/src/test/java/anotherpackage/DogTagObserverDelegatesTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019. Uber Technologies
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package anotherpackage;
17 |
18 | import static anotherpackage.DogTagTestUtil.getPreviousLineNumber;
19 | import static com.google.common.truth.Truth.assertThat;
20 | import static org.junit.Assert.fail;
21 |
22 | import com.google.common.collect.Lists;
23 | import io.reactivex.rxjava3.core.Completable;
24 | import io.reactivex.rxjava3.core.CompletableObserver;
25 | import io.reactivex.rxjava3.core.Flowable;
26 | import io.reactivex.rxjava3.core.Maybe;
27 | import io.reactivex.rxjava3.core.MaybeObserver;
28 | import io.reactivex.rxjava3.core.Observable;
29 | import io.reactivex.rxjava3.core.Observer;
30 | import io.reactivex.rxjava3.core.Single;
31 | import io.reactivex.rxjava3.core.SingleObserver;
32 | import io.reactivex.rxjava3.disposables.Disposable;
33 | import io.reactivex.rxjava3.exceptions.OnErrorNotImplementedException;
34 | import io.reactivex.rxjava3.functions.Action;
35 | import io.reactivex.rxjava3.plugins.RxJavaPlugins;
36 | import java.util.concurrent.atomic.AtomicReference;
37 | import org.junit.After;
38 | import org.junit.Before;
39 | import org.junit.Test;
40 | import org.junit.runner.RunWith;
41 | import org.junit.runners.Parameterized;
42 | import org.reactivestreams.Subscriber;
43 | import org.reactivestreams.Subscription;
44 | import rxdogtag2.RxDogTag;
45 |
46 | @RunWith(Parameterized.class)
47 | public class DogTagObserverDelegatesTest implements DogTagTest {
48 |
49 | private static final Action EMPTY_ACTION =
50 | () -> {
51 | // Noop
52 | };
53 |
54 | @Parameterized.Parameters(name = "{0}")
55 | public static Object[] data() {
56 | return new Object[] {
57 | TestParams.create(
58 | TestParamType.SYNCHRONOUS,
59 | Flowable.just(1),
60 | Flowable.never(),
61 | Flowable.empty(),
62 | Observable.just(1),
63 | Observable.never(),
64 | Observable.empty(),
65 | Maybe.just(1),
66 | Maybe.never(),
67 | Maybe.empty(),
68 | Single.just(1),
69 | Single.never(),
70 | Completable.complete(),
71 | Completable.never()),
72 | TestParams.create(
73 | TestParamType.LAZY,
74 | Flowable.fromCallable(() -> 1),
75 | Flowable.never(),
76 | Flowable.fromIterable(Lists.newArrayList()),
77 | Observable.fromCallable(() -> 1),
78 | Observable.never(),
79 | Observable.fromIterable(Lists.newArrayList()),
80 | Maybe.fromCallable(() -> 1),
81 | Maybe.never(),
82 | Maybe.fromAction(EMPTY_ACTION),
83 | Single.fromCallable(() -> 1),
84 | Single.never(),
85 | Completable.fromAction(EMPTY_ACTION),
86 | Completable.never()),
87 | TestParams.create(
88 | TestParamType.DEFERRED,
89 | Flowable.defer(() -> Flowable.fromCallable(() -> 1)),
90 | Flowable.defer(Flowable::never),
91 | Flowable.defer(() -> Flowable.fromIterable(Lists.newArrayList())),
92 | Observable.defer(() -> Observable.fromCallable(() -> 1)),
93 | Observable.defer(Observable::never),
94 | Observable.defer(() -> Observable.fromIterable(Lists.newArrayList())),
95 | Maybe.defer(() -> Maybe.fromCallable(() -> 1)),
96 | Maybe.defer(Maybe::never),
97 | Maybe.defer(() -> Maybe.fromAction(EMPTY_ACTION)),
98 | Single.defer(() -> Single.fromCallable(() -> 1)),
99 | Single.defer(Single::never),
100 | Completable.defer(() -> Completable.fromAction(EMPTY_ACTION)),
101 | Completable.defer(Completable::never))
102 | };
103 | }
104 |
105 | private final TestParams testParams;
106 |
107 | // We're still expecting the root uncaught exception handler to get hit, but by the time it
108 | // reaches here it should be the decorated exception.
109 | private AtomicReference ref;
110 |
111 | // We'll simulate an OnErrorNotImplemented exception coming from a delegate, which will happen
112 | // if a delegate hits its internal missing onError first before the DogTagObserver has a chance.
113 | private OnErrorNotImplementedException originalError;
114 |
115 | public DogTagObserverDelegatesTest(TestParams testParams) {
116 | this.testParams = testParams;
117 | }
118 |
119 | @Before
120 | public void setUp() {
121 | RxDogTag.install();
122 | ref = new AtomicReference<>();
123 | originalError = new OnErrorNotImplementedException(new RuntimeException("Blah"));
124 | }
125 |
126 | @After
127 | public void tearDown() {
128 | RxDogTag.reset();
129 | }
130 |
131 | @Test
132 | public void delegateSubscriber_throwingInOnSubscribe_shouldBeGuarded() {
133 | Thread.UncaughtExceptionHandler handler = prepareTest();
134 | Subscriber throwingObserver =
135 | new EmptySubscriber() {
136 | @Override
137 | public void onSubscribe(Subscription s) {
138 | throw originalError;
139 | }
140 | };
141 |
142 | testParams.flowableNever.subscribe(throwingObserver);
143 | assertTaggedError(getPreviousLineNumber(), handler, "onSubscribe");
144 | }
145 |
146 | @Test
147 | public void delegateSubscriber_throwingInOnNext_shouldBeGuarded() {
148 | Thread.UncaughtExceptionHandler handler = prepareTest();
149 | Subscriber throwingOnNext =
150 | new EmptySubscriber() {
151 | @Override
152 | public void onNext(Integer integer) {
153 | throw originalError;
154 | }
155 | };
156 |
157 | // For Flowable - a synchronous just() will complete in onSubscribe!
158 | String delegateType = testParams.type == TestParamType.SYNCHRONOUS ? "onSubscribe" : "onNext";
159 | testParams.flowable.subscribe(throwingOnNext);
160 | assertTaggedError(getPreviousLineNumber(), handler, delegateType);
161 | }
162 |
163 | @Test
164 | public void delegateSubscriber_throwingInOnComplete_shouldBeGuarded() {
165 | Thread.UncaughtExceptionHandler handler = prepareTest();
166 | Subscriber throwingObserver =
167 | new EmptySubscriber() {
168 | @Override
169 | public void onComplete() {
170 | throw originalError;
171 | }
172 | };
173 |
174 | testParams.flowableComplete.subscribe(throwingObserver);
175 | assertTaggedError(getPreviousLineNumber(), handler, "onComplete");
176 | }
177 |
178 | @Test
179 | public void delegateObserver_throwingInOnSubscribe_shouldBeGuarded() {
180 | Thread.UncaughtExceptionHandler handler = prepareTest();
181 | Observer throwingObserver =
182 | new EmptyObserver() {
183 | @Override
184 | public void onSubscribe(Disposable d) {
185 | throw originalError;
186 | }
187 | };
188 |
189 | testParams.observableNever.subscribe(throwingObserver);
190 | assertTaggedError(getPreviousLineNumber(), handler, "onSubscribe");
191 | }
192 |
193 | @Test
194 | public void delegateObserver_throwingInOnNext_shouldBeGuarded() {
195 | Thread.UncaughtExceptionHandler handler = prepareTest();
196 | Observer throwingObserver =
197 | new EmptyObserver() {
198 | @Override
199 | public void onNext(Integer integer) {
200 | throw originalError;
201 | }
202 | };
203 |
204 | testParams.observable.subscribe(throwingObserver);
205 | assertTaggedError(getPreviousLineNumber(), handler, "onNext");
206 | }
207 |
208 | @Test
209 | public void delegateObserver_throwingInOnComplete_shouldBeGuarded() {
210 | Thread.UncaughtExceptionHandler handler = prepareTest();
211 | Observer throwingObserver =
212 | new EmptyObserver() {
213 | @Override
214 | public void onComplete() {
215 | throw originalError;
216 | }
217 | };
218 |
219 | testParams.observableComplete.subscribe(throwingObserver);
220 | assertTaggedError(getPreviousLineNumber(), handler, "onComplete");
221 | }
222 |
223 | @Test
224 | public void delegateSingleObserver_throwingInOnSubscribe_shouldBeGuarded() {
225 | Thread.UncaughtExceptionHandler handler = prepareTest();
226 | SingleObserver throwingObserver =
227 | new EmptySingleObserver() {
228 | @Override
229 | public void onSubscribe(Disposable d) {
230 | throw originalError;
231 | }
232 | };
233 |
234 | testParams.singleNever.subscribe(throwingObserver);
235 | assertTaggedError(getPreviousLineNumber(), handler, "onSubscribe");
236 | }
237 |
238 | @Test
239 | public void delegateSingleObserver_throwingInOnSuccess_shouldBeGuarded() {
240 | Thread.UncaughtExceptionHandler handler = prepareTest();
241 | SingleObserver throwingObserver =
242 | new EmptySingleObserver() {
243 | @Override
244 | public void onSuccess(Integer integer) {
245 | throw originalError;
246 | }
247 | };
248 |
249 | testParams.single.subscribe(throwingObserver);
250 | assertTaggedError(getPreviousLineNumber(), handler, "onSuccess");
251 | }
252 |
253 | @Test
254 | public void delegateMaybeObserver_throwingInOnSubscribe_shouldBeGuarded() {
255 | Thread.UncaughtExceptionHandler handler = prepareTest();
256 | MaybeObserver throwingObserver =
257 | new EmptyMaybeObserver() {
258 | @Override
259 | public void onSubscribe(Disposable d) {
260 | throw originalError;
261 | }
262 | };
263 |
264 | testParams.maybeNever.subscribe(throwingObserver);
265 | assertTaggedError(getPreviousLineNumber(), handler, "onSubscribe");
266 | }
267 |
268 | @Test
269 | public void delegateMaybeObserver_throwingInOnSuccess_shouldBeGuarded() {
270 | Thread.UncaughtExceptionHandler handler = prepareTest();
271 | MaybeObserver throwingObserver =
272 | new EmptyMaybeObserver() {
273 | @Override
274 | public void onSuccess(Integer integer) {
275 | throw originalError;
276 | }
277 | };
278 |
279 | testParams.maybe.subscribe(throwingObserver);
280 | assertTaggedError(getPreviousLineNumber(), handler, "onSuccess");
281 | }
282 |
283 | @Test
284 | public void delegateMaybeObserver_throwingInOnComplete_shouldBeGuarded() {
285 | Thread.UncaughtExceptionHandler handler = prepareTest();
286 | MaybeObserver throwingObserver =
287 | new EmptyMaybeObserver() {
288 | @Override
289 | public void onComplete() {
290 | throw originalError;
291 | }
292 | };
293 |
294 | testParams.maybeComplete.subscribe(throwingObserver);
295 | assertTaggedError(getPreviousLineNumber(), handler, "onComplete");
296 | }
297 |
298 | @Test
299 | public void delegateCompletableObserver_throwingInOnSubscribe_shouldBeGuarded() {
300 | Thread.UncaughtExceptionHandler handler = prepareTest();
301 | CompletableObserver throwingObserver =
302 | new EmptyCompletableObserver() {
303 | @Override
304 | public void onSubscribe(Disposable d) {
305 | throw originalError;
306 | }
307 | };
308 |
309 | testParams.completableNever.subscribe(throwingObserver);
310 | assertTaggedError(getPreviousLineNumber(), handler, "onSubscribe");
311 | }
312 |
313 | @Test
314 | public void delegateCompletableObserver_throwingInOnComplete_shouldBeGuarded() {
315 | Thread.UncaughtExceptionHandler handler = prepareTest();
316 | CompletableObserver throwingObserver =
317 | new EmptyCompletableObserver() {
318 | @Override
319 | public void onComplete() {
320 | throw originalError;
321 | }
322 | };
323 |
324 | testParams.completable.subscribe(throwingObserver);
325 | assertTaggedError(getPreviousLineNumber(), handler, "onComplete");
326 | }
327 |
328 | @SuppressWarnings("NullAway") // https://github.com/uber/NullAway/issues/301
329 | private void assertTaggedError(
330 | int lineNumber, Thread.UncaughtExceptionHandler handler, String delegateType) {
331 | Throwable recordedThrowable = ref.get();
332 | assertThat(recordedThrowable).isNotNull();
333 | assertUnwrappedError(recordedThrowable, lineNumber, originalError, delegateType);
334 |
335 | // Make sure we've restored the original handler
336 | assertThat(Thread.currentThread().getUncaughtExceptionHandler()).isSameInstanceAs(handler);
337 | }
338 |
339 | private Thread.UncaughtExceptionHandler prepareTest() {
340 | // Taking back control from the RxErrorsRule for this test
341 | RxJavaPlugins.setErrorHandler(null);
342 |
343 | Thread.UncaughtExceptionHandler handler =
344 | (t, e) -> {
345 | if (ref.compareAndSet(null, e)) {
346 | return;
347 | }
348 | // This should not be hit a second time
349 | fail("This shouldn't be hit a second time. " + e);
350 | };
351 | Thread.currentThread().setUncaughtExceptionHandler(handler);
352 | return handler;
353 | }
354 |
355 | enum TestParamType {
356 | SYNCHRONOUS,
357 | LAZY,
358 | DEFERRED
359 | }
360 |
361 | static class TestParams {
362 |
363 | static TestParams create(
364 | TestParamType type,
365 | Flowable flowable,
366 | Flowable flowableNever,
367 | Flowable flowableComplete,
368 | Observable observable,
369 | Observable observableNever,
370 | Observable observableComplete,
371 | Maybe maybe,
372 | Maybe maybeNever,
373 | Maybe maybeComplete,
374 | Single single,
375 | Single singleNever,
376 | Completable completable,
377 | Completable completableNever) {
378 | return new TestParams(
379 | type,
380 | flowable,
381 | flowableNever,
382 | flowableComplete,
383 | observable,
384 | observableNever,
385 | observableComplete,
386 | maybe,
387 | maybeNever,
388 | maybeComplete,
389 | single,
390 | singleNever,
391 | completable,
392 | completableNever);
393 | }
394 |
395 | final TestParamType type;
396 | final Flowable flowable;
397 | final Flowable flowableNever;
398 | final Flowable flowableComplete;
399 | final Observable observable;
400 | final Observable observableNever;
401 | final Observable observableComplete;
402 | final Maybe maybe;
403 | final Maybe maybeNever;
404 | final Maybe maybeComplete;
405 | final Single single;
406 | final Single singleNever;
407 | final Completable completable;
408 | final Completable completableNever;
409 |
410 | TestParams(
411 | TestParamType type,
412 | Flowable flowable,
413 | Flowable flowableNever,
414 | Flowable flowableComplete,
415 | Observable observable,
416 | Observable observableNever,
417 | Observable observableComplete,
418 | Maybe maybe,
419 | Maybe maybeNever,
420 | Maybe maybeComplete,
421 | Single single,
422 | Single singleNever,
423 | Completable completable,
424 | Completable completableNever) {
425 | this.type = type;
426 | this.flowable = flowable;
427 | this.flowableNever = flowableNever;
428 | this.flowableComplete = flowableComplete;
429 | this.observable = observable;
430 | this.observableNever = observableNever;
431 | this.observableComplete = observableComplete;
432 | this.maybe = maybe;
433 | this.maybeNever = maybeNever;
434 | this.maybeComplete = maybeComplete;
435 | this.single = single;
436 | this.singleNever = singleNever;
437 | this.completable = completable;
438 | this.completableNever = completableNever;
439 | }
440 |
441 | @Override
442 | public String toString() {
443 | return type.name();
444 | }
445 | }
446 | }
447 |
--------------------------------------------------------------------------------