Skip to content

Commit b008867

Browse files
authored
Fix TCK §2.3 verification for when a legit call is made with onError/onComplete method name in the stacktrace (#483)
* Fix TCK §2.3 verification for when a legit call is made with onError/onComplete method name in the stacktrace * Name methods according to whitebox/blackbox
1 parent e403531 commit b008867

File tree

4 files changed

+356
-42
lines changed

4 files changed

+356
-42
lines changed

Diff for: tck/src/main/java/org/reactivestreams/tck/SubscriberBlackboxVerification.java

+28-16
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,12 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe
128128
blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
129129
@Override
130130
public void run(BlackboxTestStage stage) throws Throwable {
131+
final String onCompleteMethod = "required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call";
132+
131133
final Subscription subs = new Subscription() {
132134
@Override
133135
public void request(long n) {
134-
final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete");
136+
final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace(onCompleteMethod);
135137
if (onCompleteStackTraceElement.isDefined()) {
136138
final StackTraceElement stackElem = onCompleteStackTraceElement.get();
137139
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
@@ -141,7 +143,7 @@ public void request(long n) {
141143

142144
@Override
143145
public void cancel() {
144-
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete");
146+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onCompleteMethod);
145147
if (onCompleteStackElement.isDefined()) {
146148
final StackTraceElement stackElem = onCompleteStackElement.get();
147149
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
@@ -152,10 +154,15 @@ public void cancel() {
152154

153155
final Subscriber<T> sub = createSubscriber();
154156
sub.onSubscribe(subs);
155-
sub.onComplete();
157+
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(sub);
156158

157159
env.verifyNoAsyncErrorsNoDelay();
158160
}
161+
162+
/** Makes sure the onComplete is initiated with a recognizable stacktrace element on the current thread. */
163+
void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(Subscriber<?> sub) {
164+
sub.onComplete();
165+
}
159166
});
160167
}
161168

@@ -164,36 +171,41 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe
164171
blackboxSubscriberWithoutSetupTest(new BlackboxTestStageTestRun() {
165172
@Override
166173
public void run(BlackboxTestStage stage) throws Throwable {
174+
final String onErrorMethod = "required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call";
175+
167176
final Subscription subs = new Subscription() {
168177
@Override
169178
public void request(long n) {
170-
Throwable thr = new Throwable();
171-
for (StackTraceElement stackElem : thr.getStackTrace()) {
172-
if (stackElem.getMethodName().equals("onError")) {
173-
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
174-
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
175-
}
179+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod);
180+
if (onCompleteStackElement.isDefined()) {
181+
final StackTraceElement stackElem = onCompleteStackElement.get();
182+
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
183+
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
176184
}
177185
}
178186

179187
@Override
180188
public void cancel() {
181-
Throwable thr = new Throwable();
182-
for (StackTraceElement stackElem : thr.getStackTrace()) {
183-
if (stackElem.getMethodName().equals("onError")) {
184-
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
185-
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
186-
}
189+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod);
190+
if (onCompleteStackElement.isDefined()) {
191+
final StackTraceElement stackElem = onCompleteStackElement.get();
192+
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
193+
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
187194
}
188195
}
189196
};
190197

191198
final Subscriber<T> sub = createSubscriber();
192199
sub.onSubscribe(subs);
193-
sub.onError(new TestException());
200+
required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(sub);
194201

195202
env.verifyNoAsyncErrorsNoDelay();
196203
}
204+
205+
/** Makes sure the onError is initiated with a recognizable stacktrace element on the current thread. */
206+
void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(Subscriber<?> sub) {
207+
sub.onError(new TestException());
208+
}
197209
});
198210
}
199211

Diff for: tck/src/main/java/org/reactivestreams/tck/SubscriberWhiteboxVerification.java

+27-15
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,12 @@ public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComp
133133
subscriberTestWithoutSetup(new TestStageTestRun() {
134134
@Override
135135
public void run(WhiteboxTestStage stage) throws Throwable {
136+
final String onCompleteMethod = "required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call";
137+
136138
final Subscription subs = new Subscription() {
137139
@Override
138140
public void request(long n) {
139-
final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace("onComplete");
141+
final Optional<StackTraceElement> onCompleteStackTraceElement = env.findCallerMethodInStackTrace(onCompleteMethod);
140142
if (onCompleteStackTraceElement.isDefined()) {
141143
final StackTraceElement stackElem = onCompleteStackTraceElement.get();
142144
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
@@ -146,7 +148,7 @@ public void request(long n) {
146148

147149
@Override
148150
public void cancel() {
149-
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace("onComplete");
151+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onCompleteMethod);
150152
if (onCompleteStackElement.isDefined()) {
151153
final StackTraceElement stackElem = onCompleteStackElement.get();
152154
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onComplete (Rule 2.3)! (Caller: %s::%s line %d)",
@@ -159,10 +161,15 @@ public void cancel() {
159161
final Subscriber<T> sub = createSubscriber(stage.probe);
160162

161163
sub.onSubscribe(subs);
162-
sub.onComplete();
164+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(sub);
163165

164166
env.verifyNoAsyncErrorsNoDelay();
165167
}
168+
169+
/** Makes sure the onComplete is initiated with a recognizable stacktrace element on the current thread. */
170+
void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_call(Subscriber<?> sub) {
171+
sub.onComplete();
172+
}
166173
});
167174
}
168175

@@ -172,26 +179,26 @@ public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnErro
172179
subscriberTestWithoutSetup(new TestStageTestRun() {
173180
@Override
174181
public void run(WhiteboxTestStage stage) throws Throwable {
182+
final String onErrorMethod = "required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call";
183+
175184
final Subscription subs = new Subscription() {
176185
@Override
177186
public void request(long n) {
178-
Throwable thr = new Throwable();
179-
for (StackTraceElement stackElem : thr.getStackTrace()) {
180-
if (stackElem.getMethodName().equals("onError")) {
181-
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
187+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod);
188+
if (onCompleteStackElement.isDefined()) {
189+
final StackTraceElement stackElem = onCompleteStackElement.get();
190+
env.flop(String.format("Subscription::request MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
182191
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
183-
}
184192
}
185193
}
186194

187195
@Override
188196
public void cancel() {
189-
Throwable thr = new Throwable();
190-
for (StackTraceElement stackElem : thr.getStackTrace()) {
191-
if (stackElem.getMethodName().equals("onError")) {
192-
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
193-
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
194-
}
197+
final Optional<StackTraceElement> onCompleteStackElement = env.findCallerMethodInStackTrace(onErrorMethod);
198+
if (onCompleteStackElement.isDefined()) {
199+
final StackTraceElement stackElem = onCompleteStackElement.get();
200+
env.flop(String.format("Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)! (Caller: %s::%s line %d)",
201+
stackElem.getClassName(), stackElem.getMethodName(), stackElem.getLineNumber()));
195202
}
196203
}
197204
};
@@ -200,10 +207,15 @@ public void cancel() {
200207
final Subscriber<T> sub = createSubscriber(stage.probe);
201208

202209
sub.onSubscribe(subs);
203-
sub.onError(new TestException());
210+
required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(sub);
204211

205212
env.verifyNoAsyncErrorsNoDelay();
206213
}
214+
215+
/** Makes sure the onError is initiated with a recognizable stacktrace element on the current thread. */
216+
void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnError_call(Subscriber<?> sub) {
217+
sub.onError(new TestException());
218+
}
207219
});
208220
}
209221

Diff for: tck/src/test/java/org/reactivestreams/tck/SubscriberBlackboxVerificationTest.java

+165-11
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,11 @@
1111

1212
package org.reactivestreams.tck;
1313

14-
import org.reactivestreams.Subscriber;
15-
import org.reactivestreams.Subscription;
16-
import org.reactivestreams.tck.flow.support.TCKVerificationSupport;
17-
import org.testng.annotations.AfterClass;
18-
import org.testng.annotations.BeforeClass;
19-
import org.testng.annotations.Test;
20-
21-
import java.util.concurrent.CountDownLatch;
22-
import java.util.concurrent.ExecutorService;
23-
import java.util.concurrent.Executors;
24-
import java.util.concurrent.TimeUnit;
14+
import java.util.concurrent.*;
15+
16+
import org.reactivestreams.*;
17+
import org.reactivestreams.tck.flow.support.*;
18+
import org.testng.annotations.*;
2519

2620
/**
2721
* Validates that the TCK's {@link org.reactivestreams.tck.SubscriberBlackboxVerification} fails with nice human readable errors.
@@ -98,6 +92,166 @@ public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublishe
9892
}, "Subscription::cancel MUST NOT be called from Subscriber::onError (Rule 2.3)!");
9993
}
10094

95+
@Test
96+
public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnComplete() throws Throwable {
97+
customSubscriberVerification(new Subscriber<Integer>() {
98+
@Override
99+
public void onSubscribe(final Subscription s) {
100+
// emulate unrelated calls by issuing them from a method named `onComplete`
101+
new Subscriber<Object>() {
102+
@Override
103+
public void onSubscribe(Subscription s) {
104+
}
105+
106+
@Override
107+
public void onNext(Object t) {
108+
}
109+
110+
@Override
111+
public void onError(Throwable t) {
112+
}
113+
114+
@Override
115+
public void onComplete() {
116+
s.cancel();
117+
}
118+
}.onComplete();
119+
}
120+
121+
@Override
122+
public void onNext(Integer t) {
123+
}
124+
125+
@Override
126+
public void onError(Throwable t) {
127+
}
128+
129+
@Override
130+
public void onComplete() {
131+
}
132+
}).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
133+
}
134+
135+
@Test
136+
public void required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnComplete() throws Throwable {
137+
customSubscriberVerification(new Subscriber<Integer>() {
138+
@Override
139+
public void onSubscribe(final Subscription s) {
140+
// emulate unrelated calls by issuing them from a method named `onComplete`
141+
new Subscriber<Object>() {
142+
@Override
143+
public void onSubscribe(Subscription s) {
144+
}
145+
146+
@Override
147+
public void onNext(Object t) {
148+
}
149+
150+
@Override
151+
public void onError(Throwable t) {
152+
}
153+
154+
@Override
155+
public void onComplete() {
156+
s.request(1);
157+
}
158+
}.onComplete();
159+
}
160+
161+
@Override
162+
public void onNext(Integer t) {
163+
}
164+
165+
@Override
166+
public void onError(Throwable t) {
167+
}
168+
169+
@Override
170+
public void onComplete() {
171+
}
172+
}).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete();
173+
}
174+
175+
@Test
176+
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedCancelFromOnError() throws Throwable {
177+
customSubscriberVerification(new Subscriber<Integer>() {
178+
@Override
179+
public void onSubscribe(final Subscription s) {
180+
// emulate unrelated calls by issuing them from a method named `onComplete`
181+
new Subscriber<Object>() {
182+
@Override
183+
public void onSubscribe(Subscription s) {
184+
}
185+
186+
@Override
187+
public void onNext(Object t) {
188+
}
189+
190+
@Override
191+
public void onError(Throwable t) {
192+
s.cancel();
193+
}
194+
195+
@Override
196+
public void onComplete() {
197+
}
198+
}.onError(null);
199+
}
200+
201+
@Override
202+
public void onNext(Integer t) {
203+
}
204+
205+
@Override
206+
public void onError(Throwable t) {
207+
}
208+
209+
@Override
210+
public void onComplete() {
211+
}
212+
}).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
213+
}
214+
215+
@Test
216+
public void required_spec203_mustNotCallMethodsOnSubscriptionOrPublisherInOnComplete_shouldPass_unrelatedRequestFromOnError() throws Throwable {
217+
customSubscriberVerification(new Subscriber<Integer>() {
218+
@Override
219+
public void onSubscribe(final Subscription s) {
220+
// emulate unrelated calls by issuing them from a method named `onComplete`
221+
new Subscriber<Object>() {
222+
@Override
223+
public void onSubscribe(Subscription s) {
224+
}
225+
226+
@Override
227+
public void onNext(Object t) {
228+
}
229+
230+
@Override
231+
public void onError(Throwable t) {
232+
s.request(1);
233+
}
234+
235+
@Override
236+
public void onComplete() {
237+
}
238+
}.onError(null);
239+
}
240+
241+
@Override
242+
public void onNext(Integer t) {
243+
}
244+
245+
@Override
246+
public void onError(Throwable t) {
247+
}
248+
249+
@Override
250+
public void onComplete() {
251+
}
252+
}).required_spec203_blackbox_mustNotCallMethodsOnSubscriptionOrPublisherInOnError();
253+
}
254+
101255
@Test
102256
public void required_spec205_blackbox_mustCallSubscriptionCancelIfItAlreadyHasAnSubscriptionAndReceivesAnotherOnSubscribeSignal_shouldFail() throws Throwable {
103257
requireTestFailure(new ThrowingRunnable() {

0 commit comments

Comments
 (0)