mirror of https://github.com/dapr/java-sdk.git
Unit tests for actors, no state. (#117)
* actor unit tests, no state clean up a bit * Rename class * Fix merge Co-authored-by: Artur Souza <artursouza.ms@outlook.com>
This commit is contained in:
parent
8d91f9b22c
commit
04ee301371
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.dapr.actors.runtime;
|
||||||
|
|
||||||
|
import io.dapr.actors.ActorId;
|
||||||
|
import io.dapr.actors.client.ActorProxy;
|
||||||
|
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||||
|
import io.dapr.client.DaprClient;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
import java.nio.charset.IllegalCharsetNameException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ActorNoStateTest {
|
||||||
|
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||||
|
|
||||||
|
private final ActorRuntimeContext context = createContext();
|
||||||
|
private ActorManager<ActorImpl> manager = new ActorManager<>(context);
|
||||||
|
|
||||||
|
public interface MyActor {
|
||||||
|
// The test will only call the versions of this in a derived class to the user code base class.
|
||||||
|
// The user code base class version will throw.
|
||||||
|
Mono<String> stringInStringOut(String input);
|
||||||
|
Mono<Boolean> stringInBooleanOut(String input);
|
||||||
|
Mono<Void> stringInVoidOutIntentionallyThrows(String input);
|
||||||
|
Mono<MyData> classInClassOut(MyData input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ActorType(Name = "MyActor")
|
||||||
|
public static class ActorImpl extends AbstractActor implements MyActor, Actor {
|
||||||
|
private final ActorId id;
|
||||||
|
private boolean activated;
|
||||||
|
private boolean methodReturningVoidInvoked;
|
||||||
|
|
||||||
|
//public MyActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
public ActorImpl(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
super(runtimeContext, id);
|
||||||
|
this.id = id;
|
||||||
|
this.activated = true;
|
||||||
|
this.methodReturningVoidInvoked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> stringInStringOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return s + s;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
if (s.equals("true")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> stringInVoidOutIntentionallyThrows(String input) {
|
||||||
|
return Mono.fromRunnable(() -> {
|
||||||
|
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new IllegalMonitorStateException("IntentionalException");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<MyData> classInClassOut(MyData input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return new MyData(
|
||||||
|
input.getName() + input.getName(),
|
||||||
|
input.getNum() + input.getNum());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyData {
|
||||||
|
private String name;
|
||||||
|
private int num;
|
||||||
|
|
||||||
|
public MyData() {
|
||||||
|
this.name = "";
|
||||||
|
this.num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyData(String name, int num) {
|
||||||
|
this.name = name;
|
||||||
|
this.num = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNum() {
|
||||||
|
return this.num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringInStringOut() {
|
||||||
|
ActorProxy proxy = createActorProxy();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
"abcabc",
|
||||||
|
proxy.invokeActorMethod("stringInStringOut", "abc", String.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringInBooleanOut() {
|
||||||
|
ActorProxy proxy = createActorProxy();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
false,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalMonitorStateException.class)
|
||||||
|
public void stringInVoidOutIntentionallyThrows() {
|
||||||
|
ActorProxy actorProxy = createActorProxy();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
actorProxy.invokeActorMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classInClassOut() {
|
||||||
|
ActorProxy actorProxy = createActorProxy();
|
||||||
|
MyData d = new MyData("hi", 3);
|
||||||
|
|
||||||
|
// this should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
MyData response = actorProxy.invokeActorMethod("classInClassOut", d, MyData.class).block();
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
"hihi",
|
||||||
|
response.getName());
|
||||||
|
Assert.assertEquals(
|
||||||
|
6,
|
||||||
|
response.getNum());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActorId newActorId() {
|
||||||
|
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActorProxy createActorProxy() {
|
||||||
|
ActorId actorId = newActorId();
|
||||||
|
|
||||||
|
// Mock daprClient for ActorProxy only, not for runtime.
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.invokeActorMethod(
|
||||||
|
eq(context.getActorTypeInformation().getName()),
|
||||||
|
eq(actorId.toString()),
|
||||||
|
any(),
|
||||||
|
any()))
|
||||||
|
.thenAnswer(invocationOnMock ->
|
||||||
|
this.manager.invokeMethod(
|
||||||
|
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||||
|
invocationOnMock.getArgument(2, String.class),
|
||||||
|
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||||
|
invocationOnMock.getArgument(3, String.class))))
|
||||||
|
.map(s -> {
|
||||||
|
try {
|
||||||
|
return context.getActorSerializer().wrapData(s.getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.manager.activateActor(actorId).block();
|
||||||
|
|
||||||
|
return new ActorProxyForTestsImpl(
|
||||||
|
context.getActorTypeInformation().getName(),
|
||||||
|
actorId,
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
daprClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
return new ActorRuntimeContext(
|
||||||
|
mock(ActorRuntime.class),
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
new DefaultActorFactory<T>(),
|
||||||
|
ActorTypeInformation.create(ActorImpl.class),
|
||||||
|
daprClient,
|
||||||
|
mock(DaprStateAsyncProvider.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -608,7 +608,7 @@ public class ActorStatefulTest {
|
||||||
this.manager.invokeMethod(
|
this.manager.invokeMethod(
|
||||||
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||||
invocationOnMock.getArgument(2, String.class),
|
invocationOnMock.getArgument(2, String.class),
|
||||||
toStringOrNull(context.getActorSerializer().unwrapData(
|
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||||
invocationOnMock.getArgument(3, String.class))))
|
invocationOnMock.getArgument(3, String.class))))
|
||||||
.map(s -> {
|
.map(s -> {
|
||||||
try {
|
try {
|
||||||
|
@ -632,14 +632,6 @@ public class ActorStatefulTest {
|
||||||
return this.context.getActorSerializer().serializeString(params);
|
return this.context.getActorSerializer().serializeString(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String toStringOrNull(byte[] s) {
|
|
||||||
if (s == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActorId newActorId() {
|
private static ActorId newActorId() {
|
||||||
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,377 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.dapr.actors.runtime;
|
||||||
|
|
||||||
|
import io.dapr.actors.ActorId;
|
||||||
|
import io.dapr.actors.client.ActorProxy;
|
||||||
|
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||||
|
import io.dapr.client.DaprClient;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
import java.nio.charset.IllegalCharsetNameException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class DerivedActorTest {
|
||||||
|
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||||
|
|
||||||
|
private final ActorRuntimeContext context = createContext();
|
||||||
|
private ActorManager<ActorChild> manager = new ActorManager<>(context);
|
||||||
|
|
||||||
|
public interface MyActor {
|
||||||
|
|
||||||
|
// These 4 will be implemented in the user code class that extends AbstractActor, but it
|
||||||
|
// will not be implemented in another class that will inherit that.
|
||||||
|
Mono<String> onlyImplementedInParentStringInStringOut(String input);
|
||||||
|
Mono<Boolean> onlyImplementedInParentStringInBooleanOut(String input);
|
||||||
|
Mono<Void> onlyImplementedInParentStringInVoidOut(String input);
|
||||||
|
Mono<MyData> onlyImplementedInParentClassInClassOut(MyData input);
|
||||||
|
|
||||||
|
// used to validate onlyImplementedInParentStringInVoidOut() was called
|
||||||
|
boolean methodReturningVoidInvoked();
|
||||||
|
|
||||||
|
// The test will only call the versions of this in a derived class to the user code base class.
|
||||||
|
// The user code base class version will throw.
|
||||||
|
Mono<String> stringInStringOut(String input);
|
||||||
|
Mono<Boolean> stringInBooleanOut(String input);
|
||||||
|
Mono<Void> stringInVoidOut(String input);
|
||||||
|
Mono<Void> stringInVoidOutIntentionallyThrows(String input);
|
||||||
|
Mono<MyData> classInClassOut(MyData input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ActorType(Name = "MyActor")
|
||||||
|
public static class ActorParent extends AbstractActor implements MyActor, Actor {
|
||||||
|
private final ActorId id;
|
||||||
|
private boolean activated;
|
||||||
|
private boolean methodReturningVoidInvoked;
|
||||||
|
|
||||||
|
public ActorParent(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
super(runtimeContext, id);
|
||||||
|
this.id = id;
|
||||||
|
this.activated = true;
|
||||||
|
this.methodReturningVoidInvoked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> onlyImplementedInParentStringInStringOut(String input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return input + input + input;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> onlyImplementedInParentStringInBooleanOut(String input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
if (input.equals("icecream")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> onlyImplementedInParentStringInVoidOut(String input) {
|
||||||
|
return Mono.fromRunnable(() -> {
|
||||||
|
this.methodReturningVoidInvoked = true;
|
||||||
|
System.out.println("Received " + input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<MyData> onlyImplementedInParentClassInClassOut(MyData input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return new MyData(
|
||||||
|
input.getName() + input.getName() + input.getName(),
|
||||||
|
input.getNum() + input.getNum() + input.getNum());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean methodReturningVoidInvoked() {
|
||||||
|
return this.methodReturningVoidInvoked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> stringInStringOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
// In the cases below we intentionally only call the derived version of this.
|
||||||
|
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new ArithmeticException("This method should not have been called");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
// In the cases below we intentionally only call the derived version of this.
|
||||||
|
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new ArithmeticException("This method should not have been called");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> stringInVoidOut(String input) {
|
||||||
|
return Mono.fromRunnable(() -> {
|
||||||
|
this.methodReturningVoidInvoked = true;
|
||||||
|
System.out.println("Received " + input);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> stringInVoidOutIntentionallyThrows(String input) {
|
||||||
|
return Mono.fromRunnable(() -> {
|
||||||
|
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new IllegalMonitorStateException("IntentionalException");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<MyData> classInClassOut(MyData input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
// In the cases below we intentionally only call the derived version of this.
|
||||||
|
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new ArithmeticException("This method should not have been called");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ActorChild extends ActorParent implements MyActor, Actor {
|
||||||
|
private final ActorId id;
|
||||||
|
private boolean activated;
|
||||||
|
|
||||||
|
public ActorChild(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
super(runtimeContext, id);
|
||||||
|
this.id = id;
|
||||||
|
this.activated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<String> stringInStringOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return s + s;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
if (s.equals("true")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<MyData> classInClassOut(MyData input) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
return new MyData(
|
||||||
|
input.getName() + input.getName(),
|
||||||
|
input.getNum() + input.getNum());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyData {
|
||||||
|
private String name;
|
||||||
|
private int num;
|
||||||
|
|
||||||
|
public MyData() {
|
||||||
|
this.name = "";
|
||||||
|
this.num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyData(String name, int num) {
|
||||||
|
this.name = name;
|
||||||
|
this.num = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNum() {
|
||||||
|
return this.num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringInStringOut() {
|
||||||
|
ActorProxy proxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
"abcabc",
|
||||||
|
proxy.invokeActorMethod("stringInStringOut", "abc", String.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringInBooleanOut() {
|
||||||
|
ActorProxy proxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
false,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void stringInVoidOut() {
|
||||||
|
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// stringInVoidOut() has not been invoked so this is false
|
||||||
|
Assert.assertEquals(
|
||||||
|
false,
|
||||||
|
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
actorProxy.invokeActorMethod("stringInVoidOut", "hello world").block();
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalMonitorStateException.class)
|
||||||
|
public void stringInVoidOutIntentionallyThrows() {
|
||||||
|
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
actorProxy.invokeActorMethod("stringInVoidOutIntentionallyThrows", "hello world").block();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void classInClassOut() {
|
||||||
|
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||||
|
MyData d = new MyData("hi", 3);
|
||||||
|
|
||||||
|
// this should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
MyData response = actorProxy.invokeActorMethod("classInClassOut", d, MyData.class).block();
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
"hihi",
|
||||||
|
response.getName());
|
||||||
|
Assert.assertEquals(
|
||||||
|
6,
|
||||||
|
response.getNum());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actor methods this test invokes are all implemented in ActorParent only. We're asserting it's callable when the actor proxy is for an ActorChild.
|
||||||
|
@Test
|
||||||
|
public void testInheritedActorMethods() {
|
||||||
|
ActorProxy actorProxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
"www",
|
||||||
|
actorProxy.invokeActorMethod("onlyImplementedInParentStringInStringOut", "w", String.class).block());
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
actorProxy.invokeActorMethod("onlyImplementedInParentStringInBooleanOut", "icecream", Boolean.class).block());
|
||||||
|
|
||||||
|
// onlyImplementedInParentStringInVoidOut() has not been invoked so this is false
|
||||||
|
Assert.assertEquals(
|
||||||
|
false,
|
||||||
|
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||||
|
|
||||||
|
actorProxy.invokeActorMethod("onlyImplementedInParentStringInVoidOut", "icecream", Boolean.class).block();
|
||||||
|
|
||||||
|
// now it should return true.
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
actorProxy.invokeActorMethod("methodReturningVoidInvoked", Boolean.class).block());
|
||||||
|
|
||||||
|
MyData d = new MyData("hi", 3);
|
||||||
|
MyData response = actorProxy.invokeActorMethod("onlyImplementedInParentClassInClassOut", d, MyData.class).block();
|
||||||
|
|
||||||
|
Assert.assertEquals(
|
||||||
|
"hihihi",
|
||||||
|
response.getName());
|
||||||
|
Assert.assertEquals(
|
||||||
|
9,
|
||||||
|
response.getNum());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActorId newActorId() {
|
||||||
|
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActorProxy createActorProxyForActorChild() {
|
||||||
|
ActorId actorId = newActorId();
|
||||||
|
|
||||||
|
// Mock daprClient for ActorProxy only, not for runtime.
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.invokeActorMethod(
|
||||||
|
eq(context.getActorTypeInformation().getName()),
|
||||||
|
eq(actorId.toString()),
|
||||||
|
any(),
|
||||||
|
any()))
|
||||||
|
.thenAnswer(invocationOnMock ->
|
||||||
|
this.manager.invokeMethod(
|
||||||
|
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||||
|
invocationOnMock.getArgument(2, String.class),
|
||||||
|
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||||
|
invocationOnMock.getArgument(3, String.class))))
|
||||||
|
.map(s -> {
|
||||||
|
try {
|
||||||
|
return context.getActorSerializer().wrapData(s.getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.manager.activateActor(actorId).block();
|
||||||
|
|
||||||
|
return new ActorProxyForTestsImpl(
|
||||||
|
context.getActorTypeInformation().getName(),
|
||||||
|
actorId,
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
daprClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
return new ActorRuntimeContext(
|
||||||
|
mock(ActorRuntime.class),
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
new DefaultActorFactory<T>(),
|
||||||
|
ActorTypeInformation.create(ActorChild.class),
|
||||||
|
daprClient,
|
||||||
|
mock(DaprStateAsyncProvider.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.dapr.actors.runtime;
|
||||||
|
|
||||||
|
import io.dapr.actors.ActorId;
|
||||||
|
import io.dapr.actors.client.ActorProxy;
|
||||||
|
import io.dapr.actors.client.ActorProxyForTestsImpl;
|
||||||
|
import io.dapr.client.DaprClient;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.NotSerializableException;
|
||||||
|
import java.nio.charset.IllegalCharsetNameException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ThrowFromPreAndPostActorMethodsTest {
|
||||||
|
private static final AtomicInteger ACTOR_ID_COUNT = new AtomicInteger();
|
||||||
|
|
||||||
|
private final ActorRuntimeContext context = createContext();
|
||||||
|
private ActorManager<ActorChild> manager = new ActorManager<>(context);
|
||||||
|
|
||||||
|
public interface MyActor {
|
||||||
|
Mono<Boolean> stringInBooleanOut(String input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ActorType(Name = "MyActor")
|
||||||
|
public static class ActorParent extends AbstractActor implements MyActor, Actor {
|
||||||
|
private final ActorId id;
|
||||||
|
private boolean activated;
|
||||||
|
private boolean methodReturningVoidInvoked;
|
||||||
|
|
||||||
|
public ActorParent(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
super(runtimeContext, id);
|
||||||
|
this.id = id;
|
||||||
|
this.activated = true;
|
||||||
|
this.methodReturningVoidInvoked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> onPreActorMethodInternal(ActorMethodContext actorMethodContext) {
|
||||||
|
// IllegalMonitorStateException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new IllegalMonitorStateException("Intentional throw from onPreActorMethodInternal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
// In the cases below we intentionally only call the derived version of this.
|
||||||
|
// ArithmeticException is being thrown only because it's un unusual exception so it's unlikely
|
||||||
|
// to collide with something else.
|
||||||
|
throw new ArithmeticException("This method should not have been called");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ActorChild extends ActorParent implements MyActor, Actor {
|
||||||
|
private final ActorId id;
|
||||||
|
private boolean activated;
|
||||||
|
|
||||||
|
public ActorChild(ActorRuntimeContext runtimeContext, ActorId id) {
|
||||||
|
super(runtimeContext, id);
|
||||||
|
this.id = id;
|
||||||
|
this.activated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> stringInBooleanOut(String s) {
|
||||||
|
return Mono.fromSupplier(() -> {
|
||||||
|
if (s.equals("true")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class MyData {
|
||||||
|
private String name;
|
||||||
|
private int num;
|
||||||
|
|
||||||
|
public MyData() {
|
||||||
|
this.name = "";
|
||||||
|
this.num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyData(String name, int num) {
|
||||||
|
this.name = name;
|
||||||
|
this.num = num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNum() {
|
||||||
|
return this.num;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IllegalMonitorStateException should be intentionally thrown. This type was chosen for this test just because
|
||||||
|
// it is unlikely to collide.
|
||||||
|
@Test(expected = IllegalMonitorStateException.class)
|
||||||
|
public void stringInBooleanOut1() {
|
||||||
|
ActorProxy proxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
false,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "hello world", Boolean.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
// IllegalMonitorStateException should be intentionally thrown. This type was chosen for this test just because
|
||||||
|
// it is unlikely to collide.
|
||||||
|
@Test(expected = IllegalMonitorStateException.class)
|
||||||
|
public void stringInBooleanOut2() {
|
||||||
|
ActorProxy proxy = createActorProxyForActorChild();
|
||||||
|
|
||||||
|
// these should only call the actor methods for ActorChild. The implementations in ActorParent will throw.
|
||||||
|
Assert.assertEquals(
|
||||||
|
true,
|
||||||
|
proxy.invokeActorMethod("stringInBooleanOut", "true", Boolean.class).block());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ActorId newActorId() {
|
||||||
|
return new ActorId(Integer.toString(ACTOR_ID_COUNT.incrementAndGet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActorProxy createActorProxyForActorChild() {
|
||||||
|
ActorId actorId = newActorId();
|
||||||
|
|
||||||
|
// Mock daprClient for ActorProxy only, not for runtime.
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.invokeActorMethod(
|
||||||
|
eq(context.getActorTypeInformation().getName()),
|
||||||
|
eq(actorId.toString()),
|
||||||
|
any(),
|
||||||
|
any()))
|
||||||
|
.thenAnswer(invocationOnMock ->
|
||||||
|
this.manager.invokeMethod(
|
||||||
|
new ActorId(invocationOnMock.getArgument(1, String.class)),
|
||||||
|
invocationOnMock.getArgument(2, String.class),
|
||||||
|
Utilities.toStringOrNull(context.getActorSerializer().unwrapData(
|
||||||
|
invocationOnMock.getArgument(3, String.class) )))
|
||||||
|
.map(s -> {
|
||||||
|
try {
|
||||||
|
return context.getActorSerializer().wrapData(s.getBytes());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
this.manager.activateActor(actorId).block();
|
||||||
|
|
||||||
|
return new ActorProxyForTestsImpl(
|
||||||
|
context.getActorTypeInformation().getName(),
|
||||||
|
actorId,
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
daprClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T extends AbstractActor> ActorRuntimeContext createContext() {
|
||||||
|
DaprClient daprClient = mock(DaprClient.class);
|
||||||
|
|
||||||
|
when(daprClient.registerActorTimer(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.registerActorReminder(any(), any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorTimer(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
when(daprClient.unregisterActorReminder(any(), any(), any())).thenReturn(Mono.empty());
|
||||||
|
|
||||||
|
return new ActorRuntimeContext(
|
||||||
|
mock(ActorRuntime.class),
|
||||||
|
new ActorStateSerializer(),
|
||||||
|
new DefaultActorFactory<T>(),
|
||||||
|
ActorTypeInformation.create(ActorChild.class),
|
||||||
|
daprClient,
|
||||||
|
mock(DaprStateAsyncProvider.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Microsoft Corporation.
|
||||||
|
* Licensed under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.dapr.actors.runtime;
|
||||||
|
|
||||||
|
import io.dapr.actors.ActorId;
|
||||||
|
import io.dapr.client.DaprClient;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
class Utilities {
|
||||||
|
static String toStringOrNull(byte[] s) {
|
||||||
|
if (s == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(s);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue