Skip to content

Commit 423c049

Browse files
committed
Added test on ExecuteOn functionality
1 parent 134e36c commit 423c049

File tree

2 files changed

+297
-48
lines changed

2 files changed

+297
-48
lines changed

services-api/src/main/java/io/scalecube/services/Reflect.java

Lines changed: 24 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.Collections;
2323
import java.util.HashSet;
2424
import java.util.Map;
25-
import java.util.Objects;
2625
import java.util.Set;
2726
import java.util.function.Function;
2827
import java.util.stream.Collectors;
@@ -391,70 +390,47 @@ public static Scheduler executeOnScheduler(Method method, Map<String, Scheduler>
391390
if (method.isAnnotationPresent(ExecuteOn.class)) {
392391
final var executeOn = method.getAnnotation(ExecuteOn.class);
393392
final var name = executeOn.value();
394-
if (name == null || name.isEmpty()) {
395-
throw new IllegalArgumentException(
396-
"Wrong @ExecuteOn definition on "
397-
+ declaringClass.getName()
398-
+ "."
399-
+ method.getName()
400-
+ " -- value is missing");
401-
}
402393
final var scheduler = schedulers.get(name);
403394
if (scheduler == null) {
404395
throw new IllegalArgumentException(
405396
"Wrong @ExecuteOn definition on "
406397
+ declaringClass.getName()
407398
+ "."
408399
+ method.getName()
409-
+ " -- scheduler with name="
400+
+ ": scheduler (name="
410401
+ name
411-
+ " cannot be found");
402+
+ ") cannot be found");
412403
}
413404
return scheduler;
414405
}
415406

416-
// If @ExecuteOn annotation is not present on method, then find it on @Service interface
417-
418-
var clazz = declaringClass;
407+
// If @ExecuteOn annotation is not present on service method, then find it on service class
419408

420-
// Get all interfaces, including those inherited from superclasses
421-
Set<Class<?>> allInterfaces = new HashSet<>();
422-
while (clazz != null) {
423-
Collections.addAll(allInterfaces, clazz.getInterfaces());
424-
clazz = clazz.getSuperclass();
409+
ExecuteOn executeOn = null;
410+
for (var clazz = declaringClass; clazz != null; clazz = clazz.getSuperclass()) {
411+
executeOn = clazz.getAnnotation(ExecuteOn.class);
412+
if (executeOn != null) {
413+
break;
414+
}
425415
}
426416

427-
final var optional =
428-
allInterfaces.stream()
429-
.map(aClass -> aClass.getAnnotation(ExecuteOn.class))
430-
.filter(Objects::nonNull)
431-
.findFirst();
417+
if (executeOn == null) {
418+
return Schedulers.immediate();
419+
}
432420

433-
if (optional.isPresent()) {
434-
final var executeOn = optional.get();
435-
final var name = executeOn.value();
436-
if (name == null || name.isEmpty()) {
437-
throw new IllegalArgumentException(
438-
"Wrong @ExecuteOn definition on "
439-
+ declaringClass.getName()
440-
+ "."
441-
+ method.getName()
442-
+ " -- value is missing");
443-
}
444-
final var scheduler = schedulers.get(name);
445-
if (scheduler == null) {
446-
throw new IllegalArgumentException(
447-
"Wrong @ExecuteOn definition on "
448-
+ declaringClass.getName()
449-
+ "."
450-
+ method.getName()
451-
+ " -- scheduler with name="
452-
+ name
453-
+ " cannot be found");
454-
}
455-
return scheduler;
421+
final var name = executeOn.value();
422+
final var scheduler = schedulers.get(name);
423+
if (scheduler == null) {
424+
throw new IllegalArgumentException(
425+
"Wrong @ExecuteOn definition on "
426+
+ declaringClass.getName()
427+
+ "."
428+
+ method.getName()
429+
+ ": scheduler (name="
430+
+ name
431+
+ ") cannot be found");
456432
}
457433

458-
return Schedulers.immediate();
434+
return scheduler;
459435
}
460436
}
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
package io.scalecube.services;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import io.scalecube.services.Microservices.Context;
7+
import io.scalecube.services.annotations.ExecuteOn;
8+
import io.scalecube.services.annotations.Service;
9+
import io.scalecube.services.annotations.ServiceMethod;
10+
import java.util.Map;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
import java.util.concurrent.Executors;
13+
import java.util.concurrent.atomic.AtomicReference;
14+
import org.junit.jupiter.api.AfterEach;
15+
import org.junit.jupiter.api.Assertions;
16+
import org.junit.jupiter.api.BeforeEach;
17+
import org.junit.jupiter.api.Test;
18+
import reactor.core.publisher.Mono;
19+
import reactor.core.scheduler.Scheduler;
20+
import reactor.core.scheduler.Schedulers;
21+
22+
public class ExecuteOnTest extends BaseTest {
23+
24+
private static final String SCHEDULER1_NAME = "scheduler@1";
25+
private static final String SCHEDULER2_NAME = "scheduler@2";
26+
private static final String SCHEDULER3_NAME = "scheduler@3@that-was-not-declared";
27+
28+
private final Map<String, Scheduler> schedulers = new ConcurrentHashMap<>();
29+
30+
@BeforeEach
31+
void beforeEach() {
32+
schedulers.computeIfAbsent(SCHEDULER1_NAME, ExecuteOnTest::scheduler);
33+
schedulers.computeIfAbsent(SCHEDULER2_NAME, ExecuteOnTest::scheduler);
34+
}
35+
36+
private static Scheduler scheduler(String s) {
37+
return Schedulers.fromExecutor(
38+
Executors.newSingleThreadExecutor(
39+
r -> {
40+
final var thread = new Thread(r);
41+
thread.setDaemon(true);
42+
thread.setName(s);
43+
return thread;
44+
}));
45+
}
46+
47+
@AfterEach
48+
void afterEach() {
49+
schedulers.forEach((s, scheduler) -> scheduler.dispose());
50+
schedulers.clear();
51+
}
52+
53+
@Test
54+
void testExecuteOnClass() {
55+
final var executeOnClass = new HelloServiceV1();
56+
try (final var microservices =
57+
Microservices.start(
58+
new Context()
59+
.scheduler(SCHEDULER1_NAME, () -> schedulers.get(SCHEDULER1_NAME))
60+
.scheduler(SCHEDULER2_NAME, () -> schedulers.get(SCHEDULER2_NAME))
61+
.services(executeOnClass))) {
62+
63+
final var api = microservices.call().api(HelloService.class);
64+
65+
api.hello().block();
66+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
67+
68+
api.hola().block();
69+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
70+
71+
api.arigato().block();
72+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
73+
}
74+
}
75+
76+
@Test
77+
void testExecuteOnMethod() {
78+
final var executeOnClass = new HelloServiceV2();
79+
try (final var microservices =
80+
Microservices.start(
81+
new Context()
82+
.scheduler(SCHEDULER1_NAME, () -> schedulers.get(SCHEDULER1_NAME))
83+
.scheduler(SCHEDULER2_NAME, () -> schedulers.get(SCHEDULER2_NAME))
84+
.services(executeOnClass))) {
85+
86+
final var api = microservices.call().api(HelloService.class);
87+
88+
api.hello().block();
89+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
90+
91+
api.hola().block();
92+
assertEquals(SCHEDULER2_NAME, executeOnClass.threadName.get(), "threadName");
93+
94+
api.arigato().block();
95+
assertEquals("main", executeOnClass.threadName.get(), "threadName");
96+
}
97+
}
98+
99+
@Test
100+
void testExecuteOnMixedDefinition() {
101+
final var executeOnClass = new HelloServiceV3();
102+
try (final var microservices =
103+
Microservices.start(
104+
new Context()
105+
.scheduler(SCHEDULER1_NAME, () -> schedulers.get(SCHEDULER1_NAME))
106+
.scheduler(SCHEDULER2_NAME, () -> schedulers.get(SCHEDULER2_NAME))
107+
.services(executeOnClass))) {
108+
109+
final var api = microservices.call().api(HelloService.class);
110+
111+
api.hello().block();
112+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
113+
114+
api.hola().block();
115+
assertEquals(SCHEDULER1_NAME, executeOnClass.threadName.get(), "threadName");
116+
117+
api.arigato().block();
118+
assertEquals(SCHEDULER2_NAME, executeOnClass.threadName.get(), "threadName");
119+
}
120+
}
121+
122+
@Test
123+
void testExecuteOnSchedulerThatWasNotDeclared() {
124+
Assertions.assertThrows(
125+
IllegalArgumentException.class,
126+
() -> {
127+
//noinspection unused,EmptyTryBlock
128+
try (final var microservices =
129+
Microservices.start(
130+
new Context()
131+
.scheduler(SCHEDULER1_NAME, () -> schedulers.get(SCHEDULER1_NAME))
132+
.scheduler(SCHEDULER2_NAME, () -> schedulers.get(SCHEDULER2_NAME))
133+
.services(new HelloServiceV4()))) {}
134+
});
135+
}
136+
137+
@Test
138+
void testExecuteOnSchedulerMustBeDisposed() {
139+
final var s1 = Schedulers.newSingle("s1");
140+
final var s2 = Schedulers.newSingle("s2");
141+
final var s3 = Schedulers.newSingle("s3");
142+
143+
//noinspection unused,EmptyTryBlock
144+
try (final var microservices =
145+
Microservices.start(
146+
new Context()
147+
.scheduler("s1", () -> s1)
148+
.scheduler("s2", () -> s2)
149+
.scheduler("s3", () -> s3))) {}
150+
151+
assertTrue(s1.isDisposed(), "s1.isDisposed");
152+
assertTrue(s2.isDisposed(), "s2.isDisposed");
153+
assertTrue(s3.isDisposed(), "s3.isDisposed");
154+
}
155+
156+
@Service("v1/greeting")
157+
public interface HelloService {
158+
159+
@ServiceMethod
160+
Mono<String> hello();
161+
162+
@ServiceMethod
163+
Mono<String> hola();
164+
165+
@ServiceMethod
166+
Mono<String> arigato();
167+
}
168+
169+
// All methods must be executed in scheduler@1
170+
@ExecuteOn(SCHEDULER1_NAME)
171+
public static class HelloServiceV1 implements HelloService {
172+
173+
final AtomicReference<String> threadName = new AtomicReference<>();
174+
175+
@Override
176+
public Mono<String> hello() {
177+
threadName.set(Thread.currentThread().getName());
178+
return Mono.just("Hello | " + System.currentTimeMillis());
179+
}
180+
181+
@Override
182+
public Mono<String> hola() {
183+
threadName.set(Thread.currentThread().getName());
184+
return Mono.just("Hola | " + System.currentTimeMillis());
185+
}
186+
187+
@Override
188+
public Mono<String> arigato() {
189+
threadName.set(Thread.currentThread().getName());
190+
return Mono.just("Arigato | " + System.currentTimeMillis());
191+
}
192+
}
193+
194+
public static class HelloServiceV2 implements HelloService {
195+
196+
final AtomicReference<String> threadName = new AtomicReference<>();
197+
198+
// This method must be executed in the scheduler@1
199+
@ExecuteOn(SCHEDULER1_NAME)
200+
@Override
201+
public Mono<String> hello() {
202+
threadName.set(Thread.currentThread().getName());
203+
return Mono.just("Hello | " + System.currentTimeMillis());
204+
}
205+
206+
// This method must be executed in the scheduler@2
207+
@ExecuteOn(SCHEDULER2_NAME)
208+
@Override
209+
public Mono<String> hola() {
210+
threadName.set(Thread.currentThread().getName());
211+
return Mono.just("Hola | " + System.currentTimeMillis());
212+
}
213+
214+
// This method must be executed in the caller thread
215+
@Override
216+
public Mono<String> arigato() {
217+
threadName.set(Thread.currentThread().getName());
218+
return Mono.just("Arigato | " + System.currentTimeMillis());
219+
}
220+
}
221+
222+
// All methods must be executed in scheduler@1 unless they override on service method
223+
@ExecuteOn(SCHEDULER1_NAME)
224+
public static class HelloServiceV3 implements HelloService {
225+
226+
final AtomicReference<String> threadName = new AtomicReference<>();
227+
228+
@Override
229+
public Mono<String> hello() {
230+
threadName.set(Thread.currentThread().getName());
231+
return Mono.just("Hello | " + System.currentTimeMillis());
232+
}
233+
234+
@Override
235+
public Mono<String> hola() {
236+
threadName.set(Thread.currentThread().getName());
237+
return Mono.just("Hola | " + System.currentTimeMillis());
238+
}
239+
240+
// This method must be executed in the scheduler@2
241+
@ExecuteOn(SCHEDULER2_NAME)
242+
@Override
243+
public Mono<String> arigato() {
244+
threadName.set(Thread.currentThread().getName());
245+
return Mono.just("Arigato | " + System.currentTimeMillis());
246+
}
247+
}
248+
249+
// All methods must be executed in scheduler@3@that-was-not-declared
250+
@ExecuteOn(SCHEDULER3_NAME)
251+
public static class HelloServiceV4 implements HelloService {
252+
253+
final AtomicReference<String> threadName = new AtomicReference<>();
254+
255+
@Override
256+
public Mono<String> hello() {
257+
threadName.set(Thread.currentThread().getName());
258+
return Mono.just("Hello | " + System.currentTimeMillis());
259+
}
260+
261+
@Override
262+
public Mono<String> hola() {
263+
threadName.set(Thread.currentThread().getName());
264+
return Mono.just("Hola | " + System.currentTimeMillis());
265+
}
266+
267+
@Override
268+
public Mono<String> arigato() {
269+
threadName.set(Thread.currentThread().getName());
270+
return Mono.just("Arigato | " + System.currentTimeMillis());
271+
}
272+
}
273+
}

0 commit comments

Comments
 (0)