summaryrefslogtreecommitdiffstats
path: root/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap
diff options
context:
space:
mode:
Diffstat (limited to 'models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap')
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java8
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java382
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java93
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java9
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java126
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java60
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java379
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java978
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java212
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java314
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java130
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java80
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java82
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java80
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java142
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java134
-rw-r--r--models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java254
17 files changed, 3458 insertions, 5 deletions
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java
index 7ab21dece..139c5179b 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceProviderTest.java
@@ -4,7 +4,7 @@
* ================================================================================
* Copyright (C) 2018 Ericsson. All rights reserved.
* Modifications Copyright (C) 2019 Nordix Foundation.
- * Modifications Copyright (C) 2019 AT&T Intellectual Property. All rights reserved.
+ * Modifications Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,6 +28,8 @@ import static org.junit.Assert.assertNotNull;
import org.junit.Test;
import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+// TODO combine this with ActorServiceTest
+
public class ActorServiceProviderTest {
private static final String DOROTHY = "Dorothy";
@@ -37,12 +39,12 @@ public class ActorServiceProviderTest {
ActorService actorService = ActorService.getInstance();
assertNotNull(actorService);
- assertEquals(1, actorService.actors().size());
+ assertEquals(1, actorService.getActors().size());
actorService = ActorService.getInstance();
assertNotNull(actorService);
- Actor dummyActor = ActorService.getInstance().actors().get(0);
+ Actor dummyActor = ActorService.getInstance().getActors().iterator().next();
assertNotNull(dummyActor);
assertEquals("DummyActor", dummyActor.actor());
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
new file mode 100644
index 000000000..851a79129
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/ActorServiceTest.java
@@ -0,0 +1,382 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ObjectValidationResult;
+import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+
+public class ActorServiceTest {
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+ private static final String ACTOR1 = "actor A";
+ private static final String ACTOR2 = "actor B";
+ private static final String ACTOR3 = "actor C";
+ private static final String ACTOR4 = "actor D";
+
+ private Actor actor1;
+ private Actor actor2;
+ private Actor actor3;
+ private Actor actor4;
+
+ private Map<String, Object> sub1;
+ private Map<String, Object> sub2;
+ private Map<String, Object> sub3;
+ private Map<String, Object> sub4;
+ private Map<String, Object> params;
+
+ private ActorService service;
+
+
+ /**
+ * Initializes the fields, including a fully populated {@link #service}.
+ */
+ @Before
+ public void setUp() {
+ actor1 = spy(new ActorImpl(ACTOR1));
+ actor2 = spy(new ActorImpl(ACTOR2));
+ actor3 = spy(new ActorImpl(ACTOR3));
+ actor4 = spy(new ActorImpl(ACTOR4));
+
+ sub1 = Map.of("sub A", "value A");
+ sub2 = Map.of("sub B", "value B");
+ sub3 = Map.of("sub C", "value C");
+ sub4 = Map.of("sub D", "value D");
+
+ params = Map.of(ACTOR1, sub1, ACTOR2, sub2, ACTOR3, sub3, ACTOR4, sub4);
+
+ service = makeService(actor1, actor2, actor3, actor4);
+ }
+
+ @Test
+ public void testActorService() {
+ /*
+ * make a service where actors two and four have names that are duplicates of the
+ * others
+ */
+ actor2 = spy(new ActorImpl(ACTOR1));
+ actor4 = spy(new ActorImpl(ACTOR3));
+
+ service = makeService(actor1, actor2, actor3, actor4);
+
+ assertEquals(2, service.getActorNames().size());
+
+ assertSame(actor1, service.getActor(ACTOR1));
+ assertSame(actor3, service.getActor(ACTOR3));
+ }
+
+ @Test
+ public void testDoStart() {
+ service.configure(params);
+
+ setUpOp("testDoStart", actor -> when(actor.isConfigured()).thenReturn(false), Actor::start);
+
+ /*
+ * Start the service.
+ */
+ service.start();
+ assertTrue(service.isAlive());
+
+ Iterator<Actor> iter = service.getActors().iterator();
+ verify(iter.next()).start();
+ verify(iter.next(), never()).start();
+ verify(iter.next()).start();
+ verify(iter.next()).start();
+
+ // no additional types of operations
+ verify(actor1).configure(any());
+
+ // no other types of operations
+ verify(actor1, never()).stop();
+ verify(actor1, never()).shutdown();
+ }
+
+ @Test
+ public void testDoStop() {
+ service.configure(params);
+ service.start();
+
+ setUpOp("testDoStop", Actor::stop, Actor::stop);
+
+ /*
+ * Stop the service.
+ */
+ service.stop();
+ assertFalse(service.isAlive());
+
+ Iterator<Actor> iter = service.getActors().iterator();
+ verify(iter.next()).stop();
+ verify(iter.next(), times(2)).stop();
+ verify(iter.next()).stop();
+ verify(iter.next()).stop();
+
+ // no additional types of operations
+ verify(actor1).configure(any());
+ verify(actor1).start();
+
+ // no other types of operation
+ verify(actor1, never()).shutdown();
+ }
+
+ @Test
+ public void testDoShutdown() {
+ service.configure(params);
+ service.start();
+
+ setUpOp("testDoShutdown", Actor::shutdown, Actor::shutdown);
+
+ /*
+ * Shut down the service.
+ */
+ service.shutdown();
+ assertFalse(service.isAlive());
+
+ Iterator<Actor> iter = service.getActors().iterator();
+ verify(iter.next()).shutdown();
+ verify(iter.next(), times(2)).shutdown();
+ verify(iter.next()).shutdown();
+ verify(iter.next()).shutdown();
+
+ // no additional types of operations
+ verify(actor1).configure(any());
+ verify(actor1).start();
+
+ // no other types of operation
+ verify(actor1, never()).stop();
+ }
+
+ /**
+ * Applies an operation to the second actor, and then arranges for the third actor to
+ * throw an exception when its operation is performed.
+ *
+ * @param testName test name
+ * @param oper2 operation to apply to the second actor
+ * @param oper3 operation to apply to the third actor
+ */
+ private void setUpOp(String testName, Consumer<Actor> oper2, Consumer<Actor> oper3) {
+ Collection<Actor> actors = service.getActors();
+ assertEquals(testName, 4, actors.size());
+
+ Iterator<Actor> iter = actors.iterator();
+
+ // leave the first alone
+ iter.next();
+
+ // apply oper2 to the second actor
+ oper2.accept(iter.next());
+
+ // throw an exception in the third
+ oper3.accept(doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next()));
+
+ // leave the fourth alone
+ iter.next();
+ }
+
+ @Test
+ public void testGetInstance() {
+ service = ActorService.getInstance();
+ assertNotNull(service);
+
+ assertSame(service, ActorService.getInstance());
+ }
+
+ @Test
+ public void testGetActor() {
+ assertSame(actor1, service.getActor(ACTOR1));
+ assertSame(actor3, service.getActor(ACTOR3));
+
+ assertThatIllegalArgumentException().isThrownBy(() -> service.getActor("unknown actor"));
+ }
+
+ @Test
+ public void testGetActors() {
+ // @formatter:off
+ assertEquals("[actor A, actor B, actor C, actor D]",
+ service.getActors().stream()
+ .map(Actor::getName)
+ .sorted()
+ .collect(Collectors.toList())
+ .toString());
+ // @formatter:on
+ }
+
+ @Test
+ public void testGetActorNames() {
+ // @formatter:off
+ assertEquals("[actor A, actor B, actor C, actor D]",
+ service.getActorNames().stream()
+ .sorted()
+ .collect(Collectors.toList())
+ .toString());
+ // @formatter:on
+ }
+
+ @Test
+ public void testDoConfigure() {
+ service.configure(params);
+ assertTrue(service.isConfigured());
+
+ verify(actor1).configure(sub1);
+ verify(actor2).configure(sub2);
+ verify(actor3).configure(sub3);
+ verify(actor4).configure(sub4);
+
+ // no other types of operations
+ verify(actor1, never()).start();
+ verify(actor1, never()).stop();
+ verify(actor1, never()).shutdown();
+ }
+
+ /**
+ * Tests doConfigure() where actors throw parameter validation and runtime exceptions.
+ */
+ @Test
+ public void testDoConfigureExceptions() {
+ makeValidException(actor1);
+ makeRuntimeException(actor2);
+ makeValidException(actor3);
+
+ service.configure(params);
+ assertTrue(service.isConfigured());
+ }
+
+ /**
+ * Tests doConfigure(). Arranges for the following:
+ * <ul>
+ * <li>one actor is configured, but has parameters</li>
+ * <li>another actor is configured, but has no parameters</li>
+ * <li>another actor has no parameters and is not configured</li>
+ * </ul>
+ */
+ @Test
+ public void testDoConfigureConfigure() {
+ // need mutable parameters
+ params = new TreeMap<>(params);
+
+ // configure one actor
+ actor1.configure(sub1);
+
+ // configure another and remove its parameters
+ actor2.configure(sub2);
+ params.remove(ACTOR2);
+
+ // create a new, unconfigured actor
+ ActorImpl actor5 = spy(new ActorImpl("UNCONFIGURED"));
+ service = makeService(actor1, actor2, actor3, actor4, actor5);
+
+ /*
+ * Configure it.
+ */
+ service.configure(params);
+ assertTrue(service.isConfigured());
+
+ // this should have been configured again
+ verify(actor1, times(2)).configure(sub1);
+
+ // no parameters, so this should not have been configured again
+ verify(actor2).configure(sub2);
+
+ // these were only configured once
+ verify(actor3).configure(sub3);
+ verify(actor4).configure(sub4);
+
+ // never configured
+ verify(actor5, never()).configure(any());
+ assertFalse(actor5.isConfigured());
+
+ // start and verify that all are started except for the last
+ service.start();
+ verify(actor1).start();
+ verify(actor2).start();
+ verify(actor3).start();
+ verify(actor4).start();
+ verify(actor5, never()).start();
+ }
+
+ /**
+ * Arranges for an actor to throw a validation exception when
+ * {@link Actor#configure(Map)} is invoked.
+ *
+ * @param actor actor of interest
+ */
+ private void makeValidException(Actor actor) {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(
+ new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null"));
+ doThrow(ex).when(actor).configure(any());
+ }
+
+ /**
+ * Arranges for an actor to throw a runtime exception when
+ * {@link Actor#configure(Map)} is invoked.
+ *
+ * @param actor actor of interest
+ */
+ private void makeRuntimeException(Actor actor) {
+ IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION);
+ doThrow(ex).when(actor).configure(any());
+ }
+
+ @Test
+ public void testLoadActors() {
+ assertFalse(ActorService.getInstance().getActors().isEmpty());
+ assertNotNull(ActorService.getInstance().getActor("DummyActor"));
+ }
+
+ /**
+ * Makes an actor service whose {@link ActorService#loadActors()} method returns the
+ * given actors.
+ *
+ * @param actors actors to be returned
+ * @return a new actor service
+ */
+ private ActorService makeService(Actor... actors) {
+ return new ActorService() {
+ @Override
+ protected Iterable<Actor> loadActors() {
+ return Arrays.asList(actors);
+ }
+ };
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java
new file mode 100644
index 000000000..5b9856f41
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DelayedIdentStringTest.java
@@ -0,0 +1,93 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class DelayedIdentStringTest {
+
+ private int countToStringCalls;
+ private Object object;
+ private DelayedIdentString delay;
+
+ /**
+ * Initializes fields, including {@link #delay}.
+ */
+ @Before
+ public void setUp() {
+ countToStringCalls = 0;
+
+ object = new Object() {
+ @Override
+ public String toString() {
+ ++countToStringCalls;
+ return super.toString();
+ }
+ };
+
+ delay = new DelayedIdentString(object);
+ }
+
+ @Test
+ public void testToString() {
+ String delayed = delay.toString();
+ assertEquals(1, countToStringCalls);
+
+ String real = object.toString();
+ assertNotEquals(real, delayed);
+
+ assertThat(delayed).startsWith("@");
+ assertTrue(delayed.length() > 1);
+
+ // test case where the object is null
+ assertEquals(DelayedIdentString.NULL_STRING, new DelayedIdentString(null).toString());
+
+ // test case where the object returns null from toString()
+ object = new Object() {
+ @Override
+ public String toString() {
+ return null;
+ }
+ };
+ assertEquals(DelayedIdentString.NULL_STRING, new DelayedIdentString(object).toString());
+
+ // test case where the object's toString() does not include "@"
+ object = new Object() {
+ @Override
+ public String toString() {
+ return "some text";
+ }
+ };
+ assertEquals(object.toString(), new DelayedIdentString(object).toString());
+ }
+
+ @Test
+ public void testDelayedIdentString() {
+ // should not have called the object's toString() method yet
+ assertEquals(0, countToStringCalls);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java
index e9cf238e2..76cadffa6 100644
--- a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/DummyActor.java
@@ -4,6 +4,7 @@
* ================================================================================
* Copyright (C) 2018 Ericsson. All rights reserved.
* Modifications Copyright (C) 2019 Nordix Foundation.
+ * Modifications Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
* ================================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,10 +24,14 @@ package org.onap.policy.controlloop.actorserviceprovider;
import java.util.ArrayList;
import java.util.List;
+import org.onap.policy.controlloop.actorserviceprovider.impl.ActorImpl;
-import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+public class DummyActor extends ActorImpl {
+
+ public DummyActor() {
+ super(DummyActor.class.getSimpleName());
+ }
-public class DummyActor implements Actor {
@Override
public String actor() {
return this.getClass().getSimpleName();
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
new file mode 100644
index 000000000..c652e8374
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/UtilTest.java
@@ -0,0 +1,126 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import lombok.Builder;
+import lombok.Data;
+import org.junit.Test;
+
+public class UtilTest {
+
+ @Test
+ public void testIdent() {
+ Object object = new Object();
+ String result = Util.ident(object).toString();
+
+ assertNotEquals(object.toString(), result);
+ assertThat(result).startsWith("@");
+ assertTrue(result.length() > 1);
+ }
+
+ @Test
+ public void testLogException() {
+ // no exception, no log
+ AtomicInteger count = new AtomicInteger();
+ Util.logException(() -> count.incrementAndGet(), "no error");
+ assertEquals(1, count.get());
+
+ // with an exception
+ Runnable runnable = () -> {
+ count.incrementAndGet();
+ throw new IllegalStateException("expected exception");
+ };
+
+ Util.logException(runnable, "error with no args");
+ Util.logException(runnable, "error {} {} arg(s)", "with", 1);
+ }
+
+ @Test
+ public void testTranslate() {
+ // Abc => Abc
+ final Abc abc = Abc.builder().intValue(1).strValue("hello").anotherString("another").build();
+ Abc abc2 = Util.translate("abc to abc", abc, Abc.class);
+ assertEquals(abc, abc2);
+
+ // Abc => Similar
+ Similar sim = Util.translate("abc to similar", abc, Similar.class);
+ assertEquals(abc.getIntValue(), sim.getIntValue());
+ assertEquals(abc.getStrValue(), sim.getStrValue());
+
+ // Abc => Map
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map = Util.translate("abc to map", abc, TreeMap.class);
+ assertEquals("{anotherString=another, intValue=1, strValue=hello}", map.toString());
+
+ // Map => Map
+ @SuppressWarnings("unchecked")
+ Map<String, Object> map2 = Util.translate("map to map", map, LinkedHashMap.class);
+ assertEquals(map.toString(), map2.toString());
+
+ // Map => Abc
+ abc2 = Util.translate("map to abc", map, Abc.class);
+ assertEquals(abc, abc2);
+ }
+
+ @Test
+ public void testTranslateToMap() {
+ assertNull(Util.translateToMap("map: null", null));
+
+ // Abc => Map
+ final Abc abc = Abc.builder().intValue(2).strValue("world").anotherString("some").build();
+ Map<String, Object> map = new TreeMap<>(Util.translateToMap("map: abc to map", abc));
+ assertEquals("{anotherString=some, intValue=2, strValue=world}", map.toString());
+
+ // Map => Map
+ Map<String, Object> map2 = Util.translateToMap("map: map to map", map);
+ assertEquals(map.toString(), map2.toString());
+
+ assertThatIllegalArgumentException().isThrownBy(() -> Util.translateToMap("map: string", "some string"))
+ .withMessageContaining("map: string");
+ }
+
+ @Data
+ @Builder
+ public static class Abc {
+ private int intValue;
+ private String strValue;
+ private String anotherString;
+ }
+
+ // this shares some fields with Abc so the data should transfer
+ @Data
+ @Builder
+ public static class Similar {
+ private int intValue;
+ private String strValue;
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
new file mode 100644
index 000000000..fcc3fb12e
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/controlloop/ControlLoopEventContextTest.java
@@ -0,0 +1,60 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.controlloop;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+
+public class ControlLoopEventContextTest {
+
+ private VirtualControlLoopEvent event;
+ private ControlLoopEventContext context;
+
+ @Before
+ public void setUp() {
+ event = new VirtualControlLoopEvent();
+ context = new ControlLoopEventContext(event);
+ }
+
+ @Test
+ public void testControlLoopEventContext() {
+ assertSame(event, context.getEvent());
+ }
+
+ @Test
+ public void testContains_testGetProperty_testSetProperty() {
+ context.setProperty("abc", "a string");
+ context.setProperty("def", 100);
+
+ assertFalse(context.contains("ghi"));
+
+ String strValue = context.getProperty("abc");
+ assertEquals("a string", strValue);
+
+ int intValue = context.getProperty("def");
+ assertEquals(100, intValue);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
new file mode 100644
index 000000000..7e0c35a3f
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/ActorImplTest.java
@@ -0,0 +1,379 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ObjectValidationResult;
+import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.Operator;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+
+public class ActorImplTest {
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+ private static final String ACTOR_NAME = "my-actor";
+ private static final String OPER1 = "add";
+ private static final String OPER2 = "subtract";
+ private static final String OPER3 = "multiply";
+ private static final String OPER4 = "divide";
+
+ private MyOper oper1;
+ private MyOper oper2;
+ private MyOper oper3;
+ private MyOper oper4;
+
+ private Map<String, Object> sub1;
+ private Map<String, Object> sub2;
+ private Map<String, Object> sub3;
+ private Map<String, Object> sub4;
+ private Map<String, Object> params;
+
+ private ActorImpl actor;
+
+
+ /**
+ * Initializes the fields, including a fully populated {@link #actor}.
+ */
+ @Before
+ public void setUp() {
+ oper1 = spy(new MyOper(OPER1));
+ oper2 = spy(new MyOper(OPER2));
+ oper3 = spy(new MyOper(OPER3));
+ oper4 = spy(new MyOper(OPER4));
+
+ sub1 = Map.of("sub A", "value A");
+ sub2 = Map.of("sub B", "value B");
+ sub3 = Map.of("sub C", "value C");
+ sub4 = Map.of("sub D", "value D");
+
+ params = Map.of(OPER1, sub1, OPER2, sub2, OPER3, sub3, OPER4, sub4);
+
+ actor = makeActor(oper1, oper2, oper3, oper4);
+ }
+
+ @Test
+ public void testActorImpl_testGetName() {
+ assertEquals(ACTOR_NAME, actor.getName());
+ assertEquals(4, actor.getOperationNames().size());
+ }
+
+ @Test
+ public void testDoStart() {
+ actor.configure(params);
+ assertEquals(4, actor.getOperationNames().size());
+
+ /*
+ * arrange for second operator to be unconfigured and the third operator to throw
+ * an exception
+ */
+ Iterator<Operator> iter = actor.getOperators().iterator();
+ iter.next();
+ when(iter.next().isConfigured()).thenReturn(false);
+ when(iter.next().start()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
+
+ /*
+ * Start the actor.
+ */
+ actor.start();
+ assertTrue(actor.isAlive());
+
+ iter = actor.getOperators().iterator();
+ verify(iter.next()).start();
+ // this one isn't configured, so shouldn't attempt to start it
+ verify(iter.next(), never()).start();
+ // this one threw an exception
+ iter.next();
+ verify(iter.next()).start();
+
+ // no other types of operations
+ verify(oper1, never()).stop();
+ verify(oper1, never()).shutdown();
+ }
+
+ @Test
+ public void testDoStop() {
+ actor.configure(params);
+ actor.start();
+ assertEquals(4, actor.getOperationNames().size());
+
+ // arrange for second operator to throw an exception
+ Iterator<Operator> iter = actor.getOperators().iterator();
+ iter.next();
+ when(iter.next().stop()).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
+
+ /*
+ * Stop the actor.
+ */
+ actor.stop();
+ assertFalse(actor.isAlive());
+
+ iter = actor.getOperators().iterator();
+ verify(iter.next()).stop();
+ // this one threw an exception
+ iter.next();
+ verify(iter.next()).stop();
+ verify(iter.next()).stop();
+
+ // no additional types of operations
+ verify(oper1).configure(any());
+ verify(oper1).start();
+
+ // no other types of operation
+ verify(oper1, never()).shutdown();
+ }
+
+ @Test
+ public void testDoShutdown() {
+ actor.configure(params);
+ actor.start();
+ assertEquals(4, actor.getOperationNames().size());
+
+ // arrange for second operator to throw an exception
+ Iterator<Operator> iter = actor.getOperators().iterator();
+ iter.next();
+ doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(iter.next()).shutdown();
+
+ /*
+ * Stop the actor.
+ */
+ actor.shutdown();
+ assertFalse(actor.isAlive());
+
+ iter = actor.getOperators().iterator();
+ verify(iter.next()).shutdown();
+ // this one threw an exception
+ iter.next();
+ verify(iter.next()).shutdown();
+ verify(iter.next()).shutdown();
+
+ // no additional types of operations
+ verify(oper1).configure(any());
+ verify(oper1).start();
+
+ // no other types of operation
+ verify(oper1, never()).stop();
+ }
+
+ @Test
+ public void testSetOperators() {
+ // cannot set operators if already configured
+ actor.configure(params);
+ assertThatIllegalStateException().isThrownBy(() -> actor.setOperators(Collections.emptyList()));
+
+ /*
+ * make an actor where operators two and four have names that are duplicates of
+ * the others
+ */
+ oper2 = spy(new MyOper(OPER1));
+ oper4 = spy(new MyOper(OPER3));
+
+ actor = makeActor(oper1, oper2, oper3, oper4);
+
+ assertEquals(2, actor.getOperationNames().size());
+
+ assertSame(oper1, actor.getOperator(OPER1));
+ assertSame(oper3, actor.getOperator(OPER3));
+ }
+
+ @Test
+ public void testGetOperator() {
+ assertSame(oper1, actor.getOperator(OPER1));
+ assertSame(oper3, actor.getOperator(OPER3));
+
+ assertThatIllegalArgumentException().isThrownBy(() -> actor.getOperator("unknown name"));
+ }
+
+ @Test
+ public void testGetOperators() {
+ // @formatter:off
+ assertEquals("[add, divide, multiply, subtract]",
+ actor.getOperators().stream()
+ .map(Operator::getName)
+ .sorted()
+ .collect(Collectors.toList())
+ .toString());
+ // @formatter:on
+ }
+
+ @Test
+ public void testGetOperationNames() {
+ // @formatter:off
+ assertEquals("[add, divide, multiply, subtract]",
+ actor.getOperationNames().stream()
+ .sorted()
+ .collect(Collectors.toList())
+ .toString());
+ // @formatter:on
+ }
+
+ @Test
+ public void testDoConfigure() {
+ actor.configure(params);
+ assertTrue(actor.isConfigured());
+
+ verify(oper1).configure(sub1);
+ verify(oper2).configure(sub2);
+ verify(oper3).configure(sub3);
+ verify(oper4).configure(sub4);
+
+ // no other types of operations
+ verify(oper1, never()).start();
+ verify(oper1, never()).stop();
+ verify(oper1, never()).shutdown();
+ }
+
+ /**
+ * Tests doConfigure() where operators throw parameter validation and runtime
+ * exceptions.
+ */
+ @Test
+ public void testDoConfigureExceptions() {
+ makeValidException(oper1);
+ makeRuntimeException(oper2);
+ makeValidException(oper3);
+
+ actor.configure(params);
+ assertTrue(actor.isConfigured());
+ }
+
+ /**
+ * Tests doConfigure(). Arranges for the following:
+ * <ul>
+ * <li>one operator is configured, but has parameters</li>
+ * <li>another operator is configured, but has no parameters</li>
+ * <li>another operator has no parameters and is not configured</li>
+ * </ul>
+ */
+ @Test
+ public void testDoConfigureConfigure() {
+ // need mutable parameters
+ params = new TreeMap<>(params);
+
+ // configure one operator
+ oper1.configure(sub1);
+
+ // configure another and remove its parameters
+ oper2.configure(sub2);
+ params.remove(OPER2);
+
+ // create a new, unconfigured actor
+ Operator oper5 = spy(new MyOper("UNCONFIGURED"));
+ actor = makeActor(oper1, oper2, oper3, oper4, oper5);
+
+ /*
+ * Configure it.
+ */
+ actor.configure(params);
+ assertTrue(actor.isConfigured());
+
+ // this should have been configured again
+ verify(oper1, times(2)).configure(sub1);
+
+ // no parameters, so this should not have been configured again
+ verify(oper2).configure(sub2);
+
+ // these were only configured once
+ verify(oper3).configure(sub3);
+ verify(oper4).configure(sub4);
+
+ // never configured
+ verify(oper5, never()).configure(any());
+ assertFalse(oper5.isConfigured());
+
+ // start and verify that all are started except for the last
+ actor.start();
+ verify(oper1).start();
+ verify(oper2).start();
+ verify(oper3).start();
+ verify(oper4).start();
+ verify(oper5, never()).start();
+ }
+
+ /**
+ * Arranges for an operator to throw a validation exception when
+ * {@link Operator#configure(Map)} is invoked.
+ *
+ * @param oper operator of interest
+ */
+ private void makeValidException(Operator oper) {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(
+ new ObjectValidationResult(actor.getName(), null, ValidationStatus.INVALID, "null"));
+ doThrow(ex).when(oper).configure(any());
+ }
+
+ /**
+ * Arranges for an operator to throw a runtime exception when
+ * {@link Operator#configure(Map)} is invoked.
+ *
+ * @param oper operator of interest
+ */
+ private void makeRuntimeException(Operator oper) {
+ IllegalStateException ex = new IllegalStateException(EXPECTED_EXCEPTION);
+ doThrow(ex).when(oper).configure(any());
+ }
+
+ @Test
+ public void testMakeOperatorParameters() {
+ actor.configure(params);
+
+ // each operator should have received its own parameters
+ verify(oper1).configure(sub1);
+ verify(oper2).configure(sub2);
+ verify(oper3).configure(sub3);
+ verify(oper4).configure(sub4);
+ }
+
+ /**
+ * Makes an actor with the given operators.
+ *
+ * @param operators associated operators
+ * @return a new actor
+ */
+ private ActorImpl makeActor(Operator... operators) {
+ return new ActorImpl(ACTOR_NAME, operators);
+ }
+
+ private static class MyOper extends OperatorPartial implements Operator {
+
+ public MyOper(String name) {
+ super(ACTOR_NAME, name);
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
new file mode 100644
index 000000000..864ac829a
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/OperatorPartialTest.java
@@ -0,0 +1,978 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.TreeMap;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams;
+import org.onap.policy.controlloop.policy.Policy;
+import org.onap.policy.controlloop.policy.PolicyResult;
+
+public class OperatorPartialTest {
+ private static final int MAX_PARALLEL_REQUESTS = 10;
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATOR = "my-operator";
+ private static final String TARGET = "my-target";
+ private static final int TIMEOUT = 1000;
+ private static final UUID REQ_ID = UUID.randomUUID();
+
+ private static final List<PolicyResult> FAILURE_RESULTS = Arrays.asList(PolicyResult.values()).stream()
+ .filter(result -> result != PolicyResult.SUCCESS).collect(Collectors.toList());
+
+ private static final List<String> FAILURE_STRINGS =
+ FAILURE_RESULTS.stream().map(Object::toString).collect(Collectors.toList());
+
+ private VirtualControlLoopEvent event;
+ private Map<String, Object> config;
+ private ControlLoopEventContext context;
+ private MyExec executor;
+ private Policy policy;
+ private ControlLoopOperationParams params;
+
+ private MyOper oper;
+
+ private int numStart;
+ private int numEnd;
+
+ private Instant tstart;
+
+ private ControlLoopOperation opstart;
+ private ControlLoopOperation opend;
+
+ /**
+ * Initializes the fields, including {@link #oper}.
+ */
+ @Before
+ public void setUp() {
+ event = new VirtualControlLoopEvent();
+ event.setRequestId(REQ_ID);
+
+ config = new TreeMap<>();
+ context = new ControlLoopEventContext(event);
+ executor = new MyExec();
+
+ policy = new Policy();
+ policy.setActor(ACTOR);
+ policy.setRecipe(OPERATOR);
+ policy.setTimeout(TIMEOUT);
+
+ params = ControlLoopOperationParams.builder().completeCallback(this::completer).context(context)
+ .executor(executor).policy(policy).startCallback(this::starter).target(TARGET).build();
+
+ oper = new MyOper();
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ tstart = null;
+
+ opstart = null;
+ opend = null;
+ }
+
+ @Test
+ public void testOperatorPartial_testGetActorName_testGetName() {
+ assertEquals(ACTOR, oper.getActorName());
+ assertEquals(OPERATOR, oper.getName());
+ assertEquals(ACTOR + "." + OPERATOR, oper.getFullName());
+ }
+
+ @Test
+ public void testDoStart() {
+ oper = spy(new MyOper());
+
+ oper.configure(config);
+ oper.start();
+
+ verify(oper).doStart();
+
+ // others should not have been invoked
+ verify(oper, never()).doStop();
+ verify(oper, never()).doShutdown();
+ }
+
+ @Test
+ public void testDoStop() {
+ oper = spy(new MyOper());
+
+ oper.configure(config);
+ oper.start();
+ oper.stop();
+
+ verify(oper).doStop();
+
+ // should not have been re-invoked
+ verify(oper).doStart();
+
+ // others should not have been invoked
+ verify(oper, never()).doShutdown();
+ }
+
+ @Test
+ public void testDoShutdown() {
+ oper = spy(new MyOper());
+
+ oper.configure(config);
+ oper.start();
+ oper.shutdown();
+
+ verify(oper).doShutdown();
+
+ // should not have been re-invoked
+ verify(oper).doStart();
+
+ // others should not have been invoked
+ verify(oper, never()).doStop();
+ }
+
+ @Test
+ public void testStartOperation_testVerifyRunning() {
+ verifyRun("testStartOperation", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests startOperation() when the operator is not running.
+ */
+ @Test
+ public void testStartOperationNotRunning() {
+ // use a new operator, one that hasn't been started yet
+ oper = new MyOper();
+ oper.configure(new TreeMap<>());
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
+ }
+
+ /**
+ * Tests startOperation() when the operation has a preprocessor.
+ */
+ @Test
+ public void testStartOperationWithPreprocessor_testStartPreprocessor() {
+ AtomicInteger count = new AtomicInteger();
+
+ // @formatter:off
+ Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> preproc =
+ oper -> CompletableFuture.supplyAsync(() -> {
+ count.incrementAndGet();
+ oper.setOutcome(PolicyResult.SUCCESS.toString());
+ return oper;
+ }, executor);
+ // @formatter:on
+
+ oper.setPreProcessor(preproc);
+
+ verifyRun("testStartOperationWithPreprocessor_testStartPreprocessor", 1, 1, PolicyResult.SUCCESS);
+
+ assertEquals(1, count.get());
+ }
+
+ /**
+ * Tests startOperation() with multiple running requests.
+ */
+ @Test
+ public void testStartOperationMultiple() {
+ for (int count = 0; count < MAX_PARALLEL_REQUESTS; ++count) {
+ oper.startOperation(params);
+ }
+
+ assertTrue(executor.runAll());
+
+ assertNotNull(opstart);
+ assertNotNull(opend);
+ assertEquals(PolicyResult.SUCCESS.toString(), opend.getOutcome());
+
+ assertEquals(MAX_PARALLEL_REQUESTS, numStart);
+ assertEquals(MAX_PARALLEL_REQUESTS, oper.getCount());
+ assertEquals(MAX_PARALLEL_REQUESTS, numEnd);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor returns a failure.
+ */
+ @Test
+ public void testStartPreprocessorFailure() {
+ // arrange for the preprocessor to return a failure
+ oper.setPreProcessor(oper -> {
+ oper.setOutcome(PolicyResult.FAILURE_GUARD.toString());
+ return CompletableFuture.completedFuture(oper);
+ });
+
+ verifyRun("testStartPreprocessorFailure", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor throws an exception.
+ */
+ @Test
+ public void testStartPreprocessorException() {
+ // arrange for the preprocessor to throw an exception
+ oper.setPreProcessor(oper -> {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ });
+
+ verifyRun("testStartPreprocessorException", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests startPreprocessor() when the pipeline is not running.
+ */
+ @Test
+ public void testStartPreprocessorNotRunning() {
+ // arrange for the preprocessor to return success, which will be ignored
+ oper.setPreProcessor(oper -> {
+ oper.setOutcome(PolicyResult.SUCCESS.toString());
+ return CompletableFuture.completedFuture(oper);
+ });
+
+ oper.startOperation(params).cancel(false);
+ assertTrue(executor.runAll());
+
+ assertNull(opstart);
+ assertNull(opend);
+
+ assertEquals(0, numStart);
+ assertEquals(0, oper.getCount());
+ assertEquals(0, numEnd);
+ }
+
+ /**
+ * Tests startPreprocessor() when the preprocessor <b>builder</b> throws an exception.
+ */
+ @Test
+ public void testStartPreprocessorBuilderException() {
+ oper = new MyOper() {
+ @Override
+ protected Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> doPreprocessorAsFuture(
+ ControlLoopOperationParams params) {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }
+ };
+
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
+
+ // should be nothing in the queue
+ assertEquals(0, executor.getQueueLength());
+ }
+
+ @Test
+ public void testDoPreprocessorAsFuture() {
+ assertNull(oper.doPreprocessorAsFuture(params));
+ }
+
+ @Test
+ public void testStartOperationOnly_testDoOperationAsFuture() {
+ oper.startOperation(params);
+ assertTrue(executor.runAll());
+
+ assertEquals(1, oper.getCount());
+ }
+
+ /**
+ * Tests startOperationOnce() when
+ * {@link OperatorPartial#doOperationAsFuture(ControlLoopOperationParams)} throws an
+ * exception.
+ */
+ @Test
+ public void testStartOperationOnceBuilderException() {
+ oper = new MyOper() {
+ @Override
+ protected Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> doOperationAsFuture(
+ ControlLoopOperationParams params, int attempt) {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }
+ };
+
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ assertThatIllegalStateException().isThrownBy(() -> oper.startOperation(params));
+
+ // should be nothing in the queue
+ assertEquals(0, executor.getQueueLength());
+ }
+
+ @Test
+ public void testIsSuccess() {
+ ControlLoopOperation outcome = new ControlLoopOperation();
+
+ outcome.setOutcome(PolicyResult.SUCCESS.toString());
+ assertTrue(oper.isSuccess(outcome));
+
+ for (String failure : FAILURE_STRINGS) {
+ outcome.setOutcome(failure);
+ assertFalse("testIsSuccess-" + failure, oper.isSuccess(outcome));
+ }
+ }
+
+ @Test
+ public void testIsActorFailed() {
+ assertFalse(oper.isActorFailed(null));
+
+ ControlLoopOperation outcome = params.makeOutcome();
+
+ // incorrect outcome
+ outcome.setOutcome(PolicyResult.SUCCESS.toString());
+ assertFalse(oper.isActorFailed(outcome));
+
+ outcome.setOutcome(PolicyResult.FAILURE_RETRIES.toString());
+ assertFalse(oper.isActorFailed(outcome));
+
+ // correct outcome
+ outcome.setOutcome(PolicyResult.FAILURE.toString());
+
+ // incorrect actor
+ outcome.setActor(TARGET);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setActor(null);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setActor(ACTOR);
+
+ // incorrect operation
+ outcome.setOperation(TARGET);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setOperation(null);
+ assertFalse(oper.isActorFailed(outcome));
+ outcome.setOperation(OPERATOR);
+
+ // correct values
+ assertTrue(oper.isActorFailed(outcome));
+ }
+
+ @Test
+ public void testDoOperation() {
+ /*
+ * Use an operator that doesn't override doOperation().
+ */
+ OperatorPartial oper2 = new OperatorPartial(ACTOR, OPERATOR) {};
+
+ oper2.configure(new TreeMap<>());
+ oper2.start();
+
+ oper2.startOperation(params);
+ assertTrue(executor.runAll());
+
+ assertNotNull(opend);
+ assertEquals(PolicyResult.FAILURE_EXCEPTION.toString(), opend.getOutcome());
+ }
+
+ @Test
+ public void testTimeout() throws Exception {
+
+ // use a real executor
+ params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+ // trigger timeout very quickly
+ oper = new MyOper() {
+ @Override
+ protected long getTimeOutMillis(Policy policy) {
+ return 1;
+ }
+
+ @Override
+ protected Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> doOperationAsFuture(
+ ControlLoopOperationParams params, int attempt) {
+
+ return outcome -> {
+ ControlLoopOperation outcome2 = params.makeOutcome();
+ outcome2.setOutcome(PolicyResult.SUCCESS.toString());
+
+ /*
+ * Create an incomplete future that will timeout after the operation's
+ * timeout. If it fires before the other timer, then it will return a
+ * SUCCESS outcome.
+ */
+ CompletableFuture<ControlLoopOperation> future = new CompletableFuture<>();
+ future = future.orTimeout(1, TimeUnit.SECONDS).handleAsync((unused1, unused2) -> outcome,
+ params.getExecutor());
+
+ return future;
+ };
+ }
+ };
+
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ assertEquals(PolicyResult.FAILURE_TIMEOUT.toString(), oper.startOperation(params).get().getOutcome());
+ }
+
+ /**
+ * Verifies that the timer doesn't encompass the preprocessor and doesn't stop the
+ * operation once the preprocessor completes.
+ */
+ @Test
+ public void testTimeoutInPreprocessor() throws Exception {
+
+ // use a real executor
+ params = params.toBuilder().executor(ForkJoinPool.commonPool()).build();
+
+ // trigger timeout very quickly
+ oper = new MyOper() {
+ @Override
+ protected long getTimeOutMillis(Policy policy) {
+ return 10;
+ }
+
+ @Override
+ protected Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> doPreprocessorAsFuture(
+ ControlLoopOperationParams params) {
+
+ return outcome -> {
+ outcome.setOutcome(PolicyResult.SUCCESS.toString());
+
+ /*
+ * Create an incomplete future that will timeout after the operation's
+ * timeout. If it fires before the other timer, then it will return a
+ * SUCCESS outcome.
+ */
+ CompletableFuture<ControlLoopOperation> future = new CompletableFuture<>();
+ future = future.orTimeout(200, TimeUnit.MILLISECONDS).handleAsync((unused1, unused2) -> outcome,
+ params.getExecutor());
+
+ return future;
+ };
+ }
+ };
+
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ ControlLoopOperation result = oper.startOperation(params).get();
+ assertEquals(PolicyResult.SUCCESS.toString(), result.getOutcome());
+
+ assertNotNull(opstart);
+ assertNotNull(opend);
+ assertEquals(PolicyResult.SUCCESS.toString(), opend.getOutcome());
+
+ assertEquals(1, numStart);
+ assertEquals(1, oper.getCount());
+ assertEquals(1, numEnd);
+ }
+
+ /**
+ * Tests retry functions, when the count is set to zero and retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_ZeroRetries() {
+ policy.setRetry(0);
+ oper.setMaxFailures(10);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_ZeroRetries", 1, 1, PolicyResult.FAILURE);
+ }
+
+ /**
+ * Tests retry functions, when the count is null and retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_NullRetries() {
+ policy.setRetry(null);
+ oper.setMaxFailures(10);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_NullRetries", 1, 1, PolicyResult.FAILURE);
+ }
+
+ /**
+ * Tests retry functions, when retries are exhausted.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_RetriesExhausted() {
+ final int maxRetries = 3;
+ policy.setRetry(maxRetries);
+ oper.setMaxFailures(10);
+
+ verifyRun("testVerifyRunningWhenNot", maxRetries + 1, maxRetries + 1, PolicyResult.FAILURE_RETRIES);
+ }
+
+ /**
+ * Tests retry functions, when a success follows some retries.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries() {
+ policy.setRetry(10);
+
+ final int maxFailures = 3;
+ oper.setMaxFailures(maxFailures);
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_SuccessAfterRetries", maxFailures + 1, maxFailures + 1,
+ PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests retry functions, when the outcome is {@code null}.
+ */
+ @Test
+ public void testSetRetryFlag_testRetryOnFailure_NullOutcome() {
+
+ // arrange to return null from doOperation()
+ oper = new MyOper() {
+ @Override
+ protected ControlLoopOperation doOperation(ControlLoopOperationParams params, int attempt,
+ ControlLoopOperation operation) {
+
+ // update counters
+ super.doOperation(params, attempt, operation);
+ return null;
+ }
+ };
+
+ oper.configure(new TreeMap<>());
+ oper.start();
+
+ verifyRun("testSetRetryFlag_testRetryOnFailure_NullOutcome", 1, 1, PolicyResult.FAILURE, null, noop());
+ }
+
+ @Test
+ public void testGetActorOutcome() {
+ assertNull(oper.getActorOutcome(null));
+
+ ControlLoopOperation outcome = params.makeOutcome();
+ outcome.setOutcome(TARGET);
+
+ // wrong actor - should be null
+ outcome.setActor(null);
+ assertNull(oper.getActorOutcome(outcome));
+ outcome.setActor(TARGET);
+ assertNull(oper.getActorOutcome(outcome));
+ outcome.setActor(ACTOR);
+
+ // wrong operation - should be null
+ outcome.setOperation(null);
+ assertNull(oper.getActorOutcome(outcome));
+ outcome.setOperation(TARGET);
+ assertNull(oper.getActorOutcome(outcome));
+ outcome.setOperation(OPERATOR);
+
+ assertEquals(TARGET, oper.getActorOutcome(outcome));
+ }
+
+ @Test
+ public void testOnSuccess() throws Exception {
+ AtomicInteger count = new AtomicInteger();
+
+ final Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> nextStep = oper -> {
+ count.incrementAndGet();
+ return CompletableFuture.completedFuture(oper);
+ };
+
+ // pass it a null outcome
+ ControlLoopOperation outcome = oper.onSuccess(params, nextStep).apply(null).get();
+ assertNotNull(outcome);
+ assertEquals(PolicyResult.FAILURE.toString(), outcome.getOutcome());
+ assertEquals(0, count.get());
+
+ // pass it an unpopulated (i.e., failed) outcome
+ outcome = new ControlLoopOperation();
+ assertSame(outcome, oper.onSuccess(params, nextStep).apply(outcome).get());
+ assertEquals(0, count.get());
+
+ // pass it a successful outcome
+ outcome = params.makeOutcome();
+ outcome.setOutcome(PolicyResult.SUCCESS.toString());
+ assertSame(outcome, oper.onSuccess(params, nextStep).apply(outcome).get());
+ assertEquals(PolicyResult.SUCCESS.toString(), outcome.getOutcome());
+ assertEquals(1, count.get());
+ }
+
+ /**
+ * Tests onSuccess() and handleFailure() when the outcome is a success.
+ */
+ @Test
+ public void testOnSuccessTrue_testHandleFailureTrue() {
+ // arrange to return a success from the preprocessor
+ oper.setPreProcessor(oper -> {
+ oper.setOutcome(PolicyResult.SUCCESS.toString());
+ return CompletableFuture.completedFuture(oper);
+ });
+
+ verifyRun("testOnSuccessTrue_testHandleFailureTrue", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests onSuccess() and handleFailure() when the outcome is <i>not</i> a success.
+ */
+ @Test
+ public void testOnSuccessFalse_testHandleFailureFalse() throws Exception {
+ // arrange to return a failure from the preprocessor
+ oper.setPreProcessor(oper -> {
+ oper.setOutcome(PolicyResult.FAILURE.toString());
+ return CompletableFuture.completedFuture(oper);
+ });
+
+ verifyRun("testOnSuccessFalse_testHandleFailureFalse", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ /**
+ * Tests onSuccess() and handleFailure() when the outcome is {@code null}.
+ */
+ @Test
+ public void testOnSuccessFalse_testHandleFailureNull() throws Exception {
+ // arrange to return null from the preprocessor
+ oper.setPreProcessor(oper -> {
+ return CompletableFuture.completedFuture(null);
+ });
+
+ verifyRun("testOnSuccessFalse_testHandleFailureNull", 1, 0, PolicyResult.FAILURE_GUARD);
+ }
+
+ @Test
+ public void testFromException() {
+ // arrange to generate an exception when operation runs
+ oper.setGenException(true);
+
+ verifyRun("testFromException", 1, 1, PolicyResult.FAILURE_EXCEPTION);
+ }
+
+ /**
+ * Tests fromException() when there is no exception.
+ */
+ @Test
+ public void testFromExceptionNoExcept() {
+ verifyRun("testFromExceptionNoExcept", 1, 1, PolicyResult.SUCCESS);
+ }
+
+ /**
+ * Tests verifyRunning() when the pipeline is not running.
+ */
+ @Test
+ public void testVerifyRunningWhenNot() {
+ verifyRun("testVerifyRunningWhenNot", 0, 0, PolicyResult.SUCCESS, future -> future.cancel(false));
+ }
+
+ /**
+ * Tests callbackStarted() when the pipeline has already been stopped.
+ */
+ @Test
+ public void testCallbackStartedNotRunning() {
+ AtomicReference<Future<ControlLoopOperation>> future = new AtomicReference<>();
+
+ /*
+ * arrange to stop the controller when the start-callback is invoked, but capture
+ * the outcome
+ */
+ params = params.toBuilder().startCallback(oper -> {
+ starter(oper);
+ future.get().cancel(false);
+ }).build();
+
+ future.set(oper.startOperation(params));
+ assertTrue(executor.runAll());
+
+ // should have only run once
+ assertEquals(1, numStart);
+ }
+
+ /**
+ * Tests callbackCompleted() when the pipeline has already been stopped.
+ */
+ @Test
+ public void testCallbackCompletedNotRunning() {
+ AtomicReference<Future<ControlLoopOperation>> future = new AtomicReference<>();
+
+ // arrange to stop the controller when the start-callback is invoked
+ params = params.toBuilder().startCallback(oper -> {
+ future.get().cancel(false);
+ }).build();
+
+ future.set(oper.startOperation(params));
+ assertTrue(executor.runAll());
+
+ // should not have been set
+ assertNull(opend);
+ assertEquals(0, numEnd);
+ }
+
+ @Test
+ public void testSetOutcomeControlLoopOperationThrowable() {
+ final CompletionException timex = new CompletionException(new TimeoutException(EXPECTED_EXCEPTION));
+
+ ControlLoopOperation outcome;
+
+ outcome = new ControlLoopOperation();
+ oper.setOutcome(params, outcome, timex);
+ assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.FAILURE_TIMEOUT.toString(), outcome.getOutcome());
+
+ outcome = new ControlLoopOperation();
+ oper.setOutcome(params, outcome, new IllegalStateException());
+ assertEquals(ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.FAILURE_EXCEPTION.toString(), outcome.getOutcome());
+ }
+
+ @Test
+ public void testSetOutcomeControlLoopOperationPolicyResult() {
+ ControlLoopOperation outcome;
+
+ outcome = new ControlLoopOperation();
+ oper.setOutcome(params, outcome, PolicyResult.SUCCESS);
+ assertEquals(ControlLoopOperation.SUCCESS_MSG, outcome.getMessage());
+ assertEquals(PolicyResult.SUCCESS.toString(), outcome.getOutcome());
+
+ for (PolicyResult result : FAILURE_RESULTS) {
+ outcome = new ControlLoopOperation();
+ oper.setOutcome(params, outcome, result);
+ assertEquals(result.toString(), ControlLoopOperation.FAILED_MSG, outcome.getMessage());
+ assertEquals(result.toString(), result.toString(), outcome.getOutcome());
+ }
+ }
+
+ @Test
+ public void testIsTimeout() {
+ final TimeoutException timex = new TimeoutException(EXPECTED_EXCEPTION);
+
+ assertFalse(oper.isTimeout(new IllegalStateException()));
+ assertFalse(oper.isTimeout(new IllegalStateException(timex)));
+ assertFalse(oper.isTimeout(new CompletionException(new IllegalStateException(timex))));
+ assertFalse(oper.isTimeout(new CompletionException(null)));
+ assertFalse(oper.isTimeout(new CompletionException(new CompletionException(timex))));
+
+ assertTrue(oper.isTimeout(timex));
+ assertTrue(oper.isTimeout(new CompletionException(timex)));
+ }
+
+ @Test
+ public void testGetTimeOutMillis() {
+ assertEquals(TIMEOUT * 1000, oper.getTimeOutMillis(policy));
+
+ policy.setTimeout(null);
+ assertEquals(0, oper.getTimeOutMillis(policy));
+ }
+
+ private void starter(ControlLoopOperation oper) {
+ ++numStart;
+ tstart = oper.getStart();
+ opstart = oper;
+ }
+
+ private void completer(ControlLoopOperation oper) {
+ ++numEnd;
+ opend = oper;
+ }
+
+ /**
+ * Gets a function that does nothing.
+ *
+ * @param <T> type of input parameter expected by the function
+ * @return a function that does nothing
+ */
+ private <T> Consumer<T> noop() {
+ return unused -> {
+ };
+ }
+
+ /**
+ * Verifies a run.
+ *
+ * @param testName test name
+ * @param expectedCallbacks number of callbacks expected
+ * @param expectedOperations number of operation invocations expected
+ * @param expectedResult expected outcome
+ */
+ private void verifyRun(String testName, int expectedCallbacks, int expectedOperations,
+ PolicyResult expectedResult) {
+
+ String expectedSubRequestId =
+ (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
+
+ verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, noop());
+ }
+
+ /**
+ * Verifies a run.
+ *
+ * @param testName test name
+ * @param expectedCallbacks number of callbacks expected
+ * @param expectedOperations number of operation invocations expected
+ * @param expectedResult expected outcome
+ * @param manipulator function to modify the future returned by
+ * {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before
+ * the tasks in the executor are run
+ */
+ private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
+ Consumer<CompletableFuture<ControlLoopOperation>> manipulator) {
+
+ String expectedSubRequestId =
+ (expectedResult == PolicyResult.FAILURE_EXCEPTION ? null : String.valueOf(expectedOperations));
+
+ verifyRun(testName, expectedCallbacks, expectedOperations, expectedResult, expectedSubRequestId, manipulator);
+ }
+
+ /**
+ * Verifies a run.
+ *
+ * @param testName test name
+ * @param expectedCallbacks number of callbacks expected
+ * @param expectedOperations number of operation invocations expected
+ * @param expectedResult expected outcome
+ * @param expectedSubRequestId expected sub request ID
+ * @param manipulator function to modify the future returned by
+ * {@link OperatorPartial#startOperation(ControlLoopOperationParams)} before
+ * the tasks in the executor are run
+ */
+ private void verifyRun(String testName, int expectedCallbacks, int expectedOperations, PolicyResult expectedResult,
+ String expectedSubRequestId, Consumer<CompletableFuture<ControlLoopOperation>> manipulator) {
+
+ CompletableFuture<ControlLoopOperation> future = oper.startOperation(params);
+
+ manipulator.accept(future);
+
+ assertTrue(testName, executor.runAll());
+
+ assertEquals(testName, expectedCallbacks, numStart);
+ assertEquals(testName, expectedCallbacks, numEnd);
+
+ if (expectedCallbacks > 0) {
+ assertNotNull(testName, opstart);
+ assertNotNull(testName, opend);
+ assertEquals(testName, expectedResult.toString(), opend.getOutcome());
+
+ assertSame(testName, tstart, opstart.getStart());
+ assertSame(testName, tstart, opend.getStart());
+
+ try {
+ assertTrue(future.isDone());
+ assertSame(testName, opend, future.get());
+
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+
+ if (expectedOperations > 0) {
+ assertEquals(testName, expectedSubRequestId, opend.getSubRequestId());
+ }
+ }
+
+ assertEquals(testName, expectedOperations, oper.getCount());
+ }
+
+ private static class MyOper extends OperatorPartial {
+ @Getter
+ private int count = 0;
+
+ @Setter
+ private boolean genException;
+
+ @Setter
+ private int maxFailures = 0;
+
+ @Setter
+ private Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> preProcessor;
+
+ public MyOper() {
+ super(ACTOR, OPERATOR);
+ }
+
+ @Override
+ protected ControlLoopOperation doOperation(ControlLoopOperationParams params, int attempt,
+ ControlLoopOperation operation) {
+ ++count;
+ if (genException) {
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }
+
+ operation.setSubRequestId(String.valueOf(attempt));
+
+ if (count > maxFailures) {
+ operation.setOutcome(PolicyResult.SUCCESS.toString());
+ } else {
+ operation.setOutcome(PolicyResult.FAILURE.toString());
+ }
+
+ return operation;
+ }
+
+ @Override
+ protected Function<ControlLoopOperation, CompletableFuture<ControlLoopOperation>> doPreprocessorAsFuture(
+ ControlLoopOperationParams params) {
+
+ return (preProcessor != null ? preProcessor : super.doPreprocessorAsFuture(params));
+ }
+ }
+
+ /**
+ * Executor that will run tasks until the queue is empty or a maximum number of tasks
+ * have been executed.
+ */
+ private static class MyExec implements Executor {
+ private static final int MAX_TASKS = MAX_PARALLEL_REQUESTS * 100;
+
+ private Queue<Runnable> commands = new LinkedList<>();
+
+ public MyExec() {
+ // do nothing
+ }
+
+ public int getQueueLength() {
+ return commands.size();
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ commands.add(command);
+ }
+
+ public boolean runAll() {
+ for (int count = 0; count < MAX_TASKS && !commands.isEmpty(); ++count) {
+ commands.remove().run();
+ }
+
+ return commands.isEmpty();
+ }
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java
new file mode 100644
index 000000000..7a822c1d9
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/impl/StartConfigPartialTest.java
@@ -0,0 +1,212 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.impl;
+
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class StartConfigPartialTest {
+ private static final IllegalArgumentException EXPECTED_EXCEPTION =
+ new IllegalArgumentException("expected exception");
+ private static final String MY_NAME = "my-name";
+ private static final String PARAMS = "config data";
+ private static final String PARAMS2 = "config data #2";
+ private static final String PARAMSX = "config data exception";
+
+ private StartConfigPartial<String> config;
+
+ /**
+ * Creates a config whose doXxx() methods do nothing.
+ */
+ @Before
+ public void setUp() {
+ config = new StartConfigPartial<>(MY_NAME) {
+ @Override
+ protected void doConfigure(String parameters) {
+ // do nothing
+ }
+
+ @Override
+ protected void doStart() {
+ // do nothing
+ }
+
+ @Override
+ protected void doStop() {
+ // do nothing
+ }
+
+ @Override
+ protected void doShutdown() {
+ // do nothing
+ }
+ };
+
+ config = spy(config);
+ }
+
+ @Test
+ public void testConfigImpl_testGetFullName() {
+ assertEquals(MY_NAME, config.getFullName());
+ }
+
+ @Test
+ public void testIsAlive() {
+ assertFalse(config.isAlive());
+ }
+
+ @Test
+ public void testIsConfigured_testConfigure() {
+ // throw an exception during doConfigure(), but should remain unconfigured
+ assertFalse(config.isConfigured());
+ doThrow(EXPECTED_EXCEPTION).when(config).doConfigure(PARAMSX);
+ assertThatIllegalArgumentException().isThrownBy(() -> config.configure(PARAMSX)).isEqualTo(EXPECTED_EXCEPTION);
+ assertFalse(config.isConfigured());
+
+ assertFalse(config.isConfigured());
+ config.configure(PARAMS);
+ verify(config).doConfigure(PARAMS);
+ assertTrue(config.isConfigured());
+
+ // should not be able to re-configure while running
+ config.start();
+ assertThatIllegalStateException().isThrownBy(() -> config.configure(PARAMS2)).withMessageContaining(MY_NAME);
+ verify(config, never()).doConfigure(PARAMS2);
+
+ // should be able to re-configure after stopping
+ config.stop();
+ config.configure(PARAMS2);
+ verify(config).doConfigure(PARAMS2);
+ assertTrue(config.isConfigured());
+
+ // should remain configured after exception
+ doThrow(EXPECTED_EXCEPTION).when(config).doConfigure(PARAMSX);
+ assertThatIllegalArgumentException().isThrownBy(() -> config.configure(PARAMSX)).isEqualTo(EXPECTED_EXCEPTION);
+ assertTrue(config.isConfigured());
+ }
+
+ @Test
+ public void testStart() {
+ assertFalse(config.isAlive());
+
+ // can't start if not configured yet
+ assertThatIllegalStateException().isThrownBy(() -> config.start()).withMessageContaining(MY_NAME);
+ assertFalse(config.isAlive());
+
+ config.configure(PARAMS);
+
+ config.start();
+ verify(config).doStart();
+ assertTrue(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ // ok to restart when running, but shouldn't invoke doStart() again
+ config.start();
+ verify(config).doStart();
+ assertTrue(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ // should never have invoked these
+ verify(config, never()).doStop();
+ verify(config, never()).doShutdown();
+
+ // throw exception when started again, but should remain stopped
+ config.stop();
+ doThrow(EXPECTED_EXCEPTION).when(config).doStart();
+ assertThatIllegalArgumentException().isThrownBy(() -> config.start()).isEqualTo(EXPECTED_EXCEPTION);
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+ }
+
+ @Test
+ public void testStop() {
+ config.configure(PARAMS);
+
+ // ok to stop if not running, but shouldn't invoke doStop()
+ config.stop();
+ verify(config, never()).doStop();
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ config.start();
+
+ // now stop should have an effect
+ config.stop();
+ verify(config).doStop();
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ // should have only invoked this once
+ verify(config).doStart();
+
+ // should never have invoked these
+ verify(config, never()).doShutdown();
+
+ // throw exception when stopped again, but should go ahead and stop
+ config.start();
+ doThrow(EXPECTED_EXCEPTION).when(config).doStop();
+ assertThatIllegalArgumentException().isThrownBy(() -> config.stop()).isEqualTo(EXPECTED_EXCEPTION);
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+ }
+
+ @Test
+ public void testShutdown() {
+ config.configure(PARAMS);
+
+ // ok to shutdown if not running, but shouldn't invoke doShutdown()
+ config.shutdown();
+ verify(config, never()).doShutdown();
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ config.start();
+
+ // now stop should have an effect
+ config.shutdown();
+ verify(config).doShutdown();
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+
+ // should have only invoked this once
+ verify(config).doStart();
+
+ // should never have invoked these
+ verify(config, never()).doStop();
+
+ // throw exception when shut down again, but should go ahead and shut down
+ config.start();
+ doThrow(EXPECTED_EXCEPTION).when(config).doShutdown();
+ assertThatIllegalArgumentException().isThrownBy(() -> config.shutdown()).isEqualTo(EXPECTED_EXCEPTION);
+ assertFalse(config.isAlive());
+ assertTrue(config.isConfigured());
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
new file mode 100644
index 000000000..0c8e77d38
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ControlLoopOperationParamsTest.java
@@ -0,0 +1,314 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.onap.policy.common.parameters.BeanValidationResult;
+import org.onap.policy.controlloop.ControlLoopOperation;
+import org.onap.policy.controlloop.VirtualControlLoopEvent;
+import org.onap.policy.controlloop.actorserviceprovider.ActorService;
+import org.onap.policy.controlloop.actorserviceprovider.Operator;
+import org.onap.policy.controlloop.actorserviceprovider.controlloop.ControlLoopEventContext;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ControlLoopOperationParams.ControlLoopOperationParamsBuilder;
+import org.onap.policy.controlloop.actorserviceprovider.spi.Actor;
+import org.onap.policy.controlloop.policy.Policy;
+
+public class ControlLoopOperationParamsTest {
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+ private static final String ACTOR = "my-actor";
+ private static final String OPERATION = "my-operation";
+ private static final String TARGET = "my-target";
+ private static final UUID REQ_ID = UUID.randomUUID();
+
+ @Mock
+ private Actor actor;
+
+ @Mock
+ private ActorService actorService;
+
+ @Mock
+ private Consumer<ControlLoopOperation> completer;
+
+ @Mock
+ private ControlLoopEventContext context;
+
+ @Mock
+ private VirtualControlLoopEvent event;
+
+ @Mock
+ private Executor executor;
+
+ @Mock
+ private CompletableFuture<ControlLoopOperation> operation;
+
+ @Mock
+ private Operator operator;
+
+ @Mock
+ private Policy policy;
+
+ @Mock
+ private Consumer<ControlLoopOperation> starter;
+
+ private ControlLoopOperationParams params;
+ private ControlLoopOperation outcome;
+
+
+ /**
+ * Initializes mocks and sets {@link #params} to a fully-loaded set of parameters.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(actorService.getActor(ACTOR)).thenReturn(actor);
+ when(actor.getOperator(OPERATION)).thenReturn(operator);
+ when(operator.startOperation(any())).thenReturn(operation);
+
+ when(event.getRequestId()).thenReturn(REQ_ID);
+
+ when(context.getEvent()).thenReturn(event);
+
+ when(policy.getActor()).thenReturn(ACTOR);
+ when(policy.getRecipe()).thenReturn(OPERATION);
+
+ params = ControlLoopOperationParams.builder().actorService(actorService).completeCallback(completer)
+ .context(context).executor(executor).policy(policy).startCallback(starter).target(TARGET)
+ .build();
+
+ outcome = params.makeOutcome();
+ }
+
+ @Test
+ public void testStart() {
+ assertSame(operation, params.start());
+
+ assertThatIllegalArgumentException().isThrownBy(() -> params.toBuilder().context(null).build().start());
+ }
+
+ @Test
+ public void testGetActor() {
+ assertEquals(ACTOR, params.getActor());
+
+ // try with null policy
+ assertEquals(ControlLoopOperationParams.UNKNOWN, params.toBuilder().policy(null).build().getActor());
+
+ // try with null name in the policy
+ when(policy.getActor()).thenReturn(null);
+ assertEquals(ControlLoopOperationParams.UNKNOWN, params.getActor());
+ }
+
+ @Test
+ public void testGetOperation() {
+ assertEquals(OPERATION, params.getOperation());
+
+ // try with null policy
+ assertEquals(ControlLoopOperationParams.UNKNOWN, params.toBuilder().policy(null).build().getOperation());
+
+ // try with null name in the policy
+ when(policy.getRecipe()).thenReturn(null);
+ assertEquals(ControlLoopOperationParams.UNKNOWN, params.getOperation());
+ }
+
+ @Test
+ public void testGetRequestId() {
+ assertSame(REQ_ID, params.getRequestId());
+
+ // try with null context
+ assertNull(params.toBuilder().context(null).build().getRequestId());
+
+ // try with null event
+ when(context.getEvent()).thenReturn(null);
+ assertNull(params.getRequestId());
+ }
+
+ @Test
+ public void testMakeOutcome() {
+ assertEquals(ACTOR, outcome.getActor());
+ assertEquals(OPERATION, outcome.getOperation());
+ checkRemainingFields("with actor");
+
+ // try again with a null policy
+ outcome = params.toBuilder().policy(null).build().makeOutcome();
+ assertEquals(ControlLoopOperationParams.UNKNOWN, outcome.getActor());
+ assertEquals(ControlLoopOperationParams.UNKNOWN, outcome.getOperation());
+ checkRemainingFields("unknown actor");
+ }
+
+ protected void checkRemainingFields(String testName) {
+ assertEquals(testName, TARGET, outcome.getTarget());
+ assertNotNull(testName, outcome.getStart());
+ assertNull(testName, outcome.getEnd());
+ assertNull(testName, outcome.getSubRequestId());
+ assertNull(testName, outcome.getOutcome());
+ assertNull(testName, outcome.getMessage());
+ }
+
+ @Test
+ public void testCallbackStarted() {
+ params.callbackStarted(outcome);
+ verify(starter).accept(outcome);
+
+ // modify starter to throw an exception
+ AtomicInteger count = new AtomicInteger();
+ doAnswer(args -> {
+ count.incrementAndGet();
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }).when(starter).accept(outcome);
+
+ params.callbackStarted(outcome);
+ verify(starter, times(2)).accept(outcome);
+ assertEquals(1, count.get());
+
+ // repeat with no start-callback - no additional calls expected
+ params.toBuilder().startCallback(null).build().callbackStarted(outcome);
+ verify(starter, times(2)).accept(outcome);
+ assertEquals(1, count.get());
+
+ // should not call complete-callback
+ verify(completer, never()).accept(any());
+ }
+
+ @Test
+ public void testCallbackCompleted() {
+ params.callbackCompleted(outcome);
+ verify(completer).accept(outcome);
+
+ // modify completer to throw an exception
+ AtomicInteger count = new AtomicInteger();
+ doAnswer(args -> {
+ count.incrementAndGet();
+ throw new IllegalStateException(EXPECTED_EXCEPTION);
+ }).when(completer).accept(outcome);
+
+ params.callbackCompleted(outcome);
+ verify(completer, times(2)).accept(outcome);
+ assertEquals(1, count.get());
+
+ // repeat with no complete-callback - no additional calls expected
+ params.toBuilder().completeCallback(null).build().callbackCompleted(outcome);
+ verify(completer, times(2)).accept(outcome);
+ assertEquals(1, count.get());
+
+ // should not call start-callback
+ verify(starter, never()).accept(any());
+ }
+
+ @Test
+ public void testValidateFields() {
+ testValidate("actorService", "null", bldr -> bldr.actorService(null));
+ testValidate("context", "null", bldr -> bldr.context(null));
+ testValidate("executor", "null", bldr -> bldr.executor(null));
+ testValidate("policy", "null", bldr -> bldr.policy(null));
+ testValidate("target", "null", bldr -> bldr.target(null));
+
+ // check edge cases
+ assertTrue(params.toBuilder().build().validate().isValid());
+
+ // these can be null
+ assertTrue(params.toBuilder().startCallback(null).completeCallback(null).build().validate().isValid());
+
+ // test with minimal fields
+ assertTrue(ControlLoopOperationParams.builder().actorService(actorService).context(context).policy(policy)
+ .target(TARGET).build().validate().isValid());
+ }
+
+ private void testValidate(String fieldName, String expected,
+ Function<ControlLoopOperationParamsBuilder, ControlLoopOperationParamsBuilder> makeInvalid) {
+
+ // original params should be valid
+ BeanValidationResult result = params.validate();
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ result = makeInvalid.apply(params.toBuilder()).build().validate();
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(fieldName).contains(expected);
+ }
+
+ @Test
+ public void testBuilder_testToBuilder() {
+ assertEquals(params, params.toBuilder().build());
+ }
+
+ @Test
+ public void testActorService() {
+ assertSame(actorService, params.getActorService());
+ }
+
+ @Test
+ public void testGetContext() {
+ assertSame(context, params.getContext());
+ }
+
+ @Test
+ public void testGetExecutor() {
+ assertSame(executor, params.getExecutor());
+
+ // should use default when unspecified
+ assertSame(ForkJoinPool.commonPool(), ControlLoopOperationParams.builder().build().getExecutor());
+ }
+
+ @Test
+ public void testGetPolicy() {
+ assertSame(policy, params.getPolicy());
+ }
+
+ @Test
+ public void testGetStartCallback() {
+ assertSame(starter, params.getStartCallback());
+ }
+
+ @Test
+ public void testGetCompleteCallback() {
+ assertSame(completer, params.getCompleteCallback());
+ }
+
+ @Test
+ public void testGetTarget() {
+ assertEquals(TARGET, params.getTarget());
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
new file mode 100644
index 000000000..1763388f2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpActorParamsTest.java
@@ -0,0 +1,130 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+
+public class HttpActorParamsTest {
+
+ private static final String CONTAINER = "my-container";
+ private static final String CLIENT = "my-client";
+ private static final long TIMEOUT = 10;
+
+ private static final String PATH1 = "path #1";
+ private static final String PATH2 = "path #2";
+ private static final String URI1 = "uri #1";
+ private static final String URI2 = "uri #2";
+
+ private Map<String, String> paths;
+ private HttpActorParams params;
+
+ /**
+ * Initializes {@link #paths} with two items and {@link params} with a fully populated
+ * object.
+ */
+ @Before
+ public void setUp() {
+ paths = new TreeMap<>();
+ paths.put(PATH1, URI1);
+ paths.put(PATH2, URI2);
+
+ params = makeHttpActorParams();
+ }
+
+ @Test
+ public void testMakeOperationParameters() {
+ Function<String, Map<String, Object>> maker = params.makeOperationParameters(CONTAINER);
+ assertNull(maker.apply("unknown-operation"));
+
+ Map<String, Object> subparam = maker.apply(PATH1);
+ assertNotNull(subparam);
+ assertEquals("{clientName=my-client, path=uri #1, timeoutSec=10}", new TreeMap<>(subparam).toString());
+
+ subparam = maker.apply(PATH2);
+ assertNotNull(subparam);
+ assertEquals("{clientName=my-client, path=uri #2, timeoutSec=10}", new TreeMap<>(subparam).toString());
+ }
+
+ @Test
+ public void testDoValidation() {
+ assertThatCode(() -> params.doValidation(CONTAINER)).doesNotThrowAnyException();
+
+ // invalid param
+ params.setClientName(null);
+ assertThatThrownBy(() -> params.doValidation(CONTAINER))
+ .isInstanceOf(ParameterValidationRuntimeException.class);
+ }
+
+ @Test
+ public void testValidate() {
+ testValidateField("clientName", "null", params2 -> params2.setClientName(null));
+ testValidateField("path", "null", params2 -> params2.setPath(null));
+ testValidateField("timeoutSec", "minimum", params2 -> params2.setTimeoutSec(-1));
+
+ // check edge cases
+ params.setTimeoutSec(0);
+ assertTrue(params.validate(CONTAINER).isValid());
+
+ params.setTimeoutSec(1);
+ assertTrue(params.validate(CONTAINER).isValid());
+
+ // one path value is null
+ testValidateField(PATH2, "null", params2 -> paths.put(PATH2, null));
+ }
+
+ private void testValidateField(String fieldName, String expected, Consumer<HttpActorParams> makeInvalid) {
+
+ // original params should be valid
+ ValidationResult result = params.validate(CONTAINER);
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ HttpActorParams params2 = makeHttpActorParams();
+ makeInvalid.accept(params2);
+ result = params2.validate(CONTAINER);
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(CONTAINER).contains(fieldName).contains(expected);
+ }
+
+ private HttpActorParams makeHttpActorParams() {
+ HttpActorParams params2 = new HttpActorParams();
+ params2.setClientName(CLIENT);
+ params2.setTimeoutSec(TIMEOUT);
+ params2.setPath(paths);
+
+ return params2;
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
new file mode 100644
index 000000000..829c480d1
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/HttpParamsTest.java
@@ -0,0 +1,80 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.HttpParams.HttpParamsBuilder;
+
+public class HttpParamsTest {
+
+ private static final String CONTAINER = "my-container";
+ private static final String CLIENT = "my-client";
+ private static final String PATH = "my-path";
+ private static final long TIMEOUT = 10;
+
+ private HttpParams params;
+
+ @Before
+ public void setUp() {
+ params = HttpParams.builder().clientName(CLIENT).path(PATH).timeoutSec(TIMEOUT).build();
+ }
+
+ @Test
+ public void testValidate() {
+ testValidateField("clientName", "null", bldr -> bldr.clientName(null));
+ testValidateField("path", "null", bldr -> bldr.path(null));
+ testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
+
+ // check edge cases
+ assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+ assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
+ }
+
+ @Test
+ public void testBuilder_testToBuilder() {
+ assertEquals(CLIENT, params.getClientName());
+ assertEquals(PATH, params.getPath());
+ assertEquals(TIMEOUT, params.getTimeoutSec());
+
+ assertEquals(params, params.toBuilder().build());
+ }
+
+ private void testValidateField(String fieldName, String expected,
+ Function<HttpParamsBuilder, HttpParamsBuilder> makeInvalid) {
+
+ // original params should be valid
+ ValidationResult result = params.validate(CONTAINER);
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ result = makeInvalid.apply(params.toBuilder()).build().validate(CONTAINER);
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(fieldName).contains(expected);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java
new file mode 100644
index 000000000..9879f604f
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/ParameterValidationRuntimeExceptionTest.java
@@ -0,0 +1,82 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ObjectValidationResult;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.common.parameters.ValidationStatus;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.ParameterValidationRuntimeException;
+
+public class ParameterValidationRuntimeExceptionTest {
+
+ private static final String THE_MESSAGE = "the message";
+ private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+
+ private ValidationResult result;
+
+ @Before
+ public void setUp() {
+ result = new ObjectValidationResult("param", null, ValidationStatus.INVALID, "null");
+ }
+
+ @Test
+ public void testParameterValidationExceptionValidationResult() {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(result);
+ assertSame(result, ex.getResult());
+ assertNull(ex.getMessage());
+ }
+
+ @Test
+ public void testParameterValidationExceptionValidationResultString() {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(THE_MESSAGE, result);
+ assertSame(result, ex.getResult());
+ assertEquals(THE_MESSAGE, ex.getMessage());
+ }
+
+ @Test
+ public void testParameterValidationExceptionValidationResultThrowable() {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(EXPECTED_EXCEPTION, result);
+ assertSame(result, ex.getResult());
+ assertEquals(EXPECTED_EXCEPTION.toString(), ex.getMessage());
+ assertEquals(EXPECTED_EXCEPTION, ex.getCause());
+ }
+
+ @Test
+ public void testParameterValidationExceptionValidationResultStringThrowable() {
+ ParameterValidationRuntimeException ex =
+ new ParameterValidationRuntimeException(THE_MESSAGE, EXPECTED_EXCEPTION, result);
+ assertSame(result, ex.getResult());
+ assertEquals(THE_MESSAGE, ex.getMessage());
+ assertEquals(EXPECTED_EXCEPTION, ex.getCause());
+ }
+
+ @Test
+ public void testGetResult() {
+ ParameterValidationRuntimeException ex = new ParameterValidationRuntimeException(result);
+ assertSame(result, ex.getResult());
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java
new file mode 100644
index 000000000..4834c98d2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/parameters/TopicParamsTest.java
@@ -0,0 +1,80 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.parameters;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.onap.policy.common.parameters.ValidationResult;
+import org.onap.policy.controlloop.actorserviceprovider.parameters.TopicParams.TopicParamsBuilder;
+
+public class TopicParamsTest {
+
+ private static final String CONTAINER = "my-container";
+ private static final String TARGET = "my-target";
+ private static final String SOURCE = "my-source";
+ private static final long TIMEOUT = 10;
+
+ private TopicParams params;
+
+ @Before
+ public void setUp() {
+ params = TopicParams.builder().target(TARGET).source(SOURCE).timeoutSec(TIMEOUT).build();
+ }
+
+ @Test
+ public void testValidate() {
+ testValidateField("target", "null", bldr -> bldr.target(null));
+ testValidateField("source", "null", bldr -> bldr.source(null));
+ testValidateField("timeoutSec", "minimum", bldr -> bldr.timeoutSec(-1));
+
+ // check edge cases
+ assertTrue(params.toBuilder().timeoutSec(0).build().validate(CONTAINER).isValid());
+ assertTrue(params.toBuilder().timeoutSec(1).build().validate(CONTAINER).isValid());
+ }
+
+ @Test
+ public void testBuilder_testToBuilder() {
+ assertEquals(TARGET, params.getTarget());
+ assertEquals(SOURCE, params.getSource());
+ assertEquals(TIMEOUT, params.getTimeoutSec());
+
+ assertEquals(params, params.toBuilder().build());
+ }
+
+ private void testValidateField(String fieldName, String expected,
+ Function<TopicParamsBuilder, TopicParamsBuilder> makeInvalid) {
+
+ // original params should be valid
+ ValidationResult result = params.validate(CONTAINER);
+ assertTrue(fieldName, result.isValid());
+
+ // make invalid params
+ result = makeInvalid.apply(params.toBuilder()).build().validate(CONTAINER);
+ assertFalse(fieldName, result.isValid());
+ assertThat(result.getResult()).contains(fieldName).contains(expected);
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java
new file mode 100644
index 000000000..de1cf0f8d
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/FutureManagerTest.java
@@ -0,0 +1,142 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.pipeline;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.Future;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class FutureManagerTest {
+
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+
+ @Mock
+ private Future<String> future1;
+
+ @Mock
+ private Future<String> future2;
+
+ @Mock
+ private Future<String> future3;
+
+ private FutureManager mgr;
+
+ /**
+ * Initializes fields, including {@link #mgr}.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mgr = new FutureManager();
+ }
+
+ @Test
+ public void testStop() {
+ mgr.add(future1);
+ mgr.add(future2);
+ mgr.add(future3);
+
+ // arrange for one to throw an exception
+ when(future2.cancel(anyBoolean())).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
+
+ // nothing should have been canceled yet
+ verify(future1, never()).cancel(anyBoolean());
+ verify(future2, never()).cancel(anyBoolean());
+ verify(future3, never()).cancel(anyBoolean());
+
+ assertTrue(mgr.isRunning());
+
+ // stop the controller
+
+ // stop the controller
+ mgr.stop();
+
+ // all controllers should now be stopped
+ assertFalse(mgr.isRunning());
+
+ // everything should have been invoked
+ verify(future1).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+ verify(future3).cancel(anyBoolean());
+
+ // re-invoking stop should have no effect on the listeners
+ mgr.stop();
+
+ verify(future1).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+ verify(future3).cancel(anyBoolean());
+ }
+
+ @Test
+ public void testAdd() {
+ // still running - this should not be invoked
+ mgr.add(future1);
+ verify(future1, never()).cancel(anyBoolean());
+
+ // re-add should have no impact
+ mgr.add(future1);
+ verify(future1, never()).cancel(anyBoolean());
+
+ mgr.stop();
+
+ verify(future1).cancel(anyBoolean());
+
+ // new additions should be invoked immediately
+ mgr.add(future2);
+ verify(future2).cancel(anyBoolean());
+
+ // should work with exceptions, too
+ when(future3.cancel(anyBoolean())).thenThrow(new IllegalStateException(EXPECTED_EXCEPTION));
+ mgr.add(future3);
+ }
+
+ @Test
+ public void testRemove() {
+ mgr.add(future1);
+ mgr.add(future2);
+
+ verify(future1, never()).cancel(anyBoolean());
+ verify(future2, never()).cancel(anyBoolean());
+
+ // remove the second
+ mgr.remove(future2);
+
+ // should be able to remove it again
+ mgr.remove(future2);
+
+ mgr.stop();
+
+ // first should have run, but not the second
+ verify(future1).cancel(anyBoolean());
+
+ verify(future2, never()).cancel(anyBoolean());
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java
new file mode 100644
index 000000000..4a882d422
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/ListenerManagerTest.java
@@ -0,0 +1,134 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.pipeline;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class ListenerManagerTest {
+
+ private static final String EXPECTED_EXCEPTION = "expected exception";
+
+ @Mock
+ private Runnable runnable1;
+
+ @Mock
+ private Runnable runnable2;
+
+ @Mock
+ private Runnable runnable3;
+
+ private ListenerManager mgr;
+
+ /**
+ * Initializes fields, including {@link #mgr}.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mgr = new ListenerManager();
+ }
+
+ @Test
+ public void testStop_testIsRunning() {
+ mgr.add(runnable1);
+ mgr.add(runnable2);
+ mgr.add(runnable3);
+
+ // arrange for one to throw an exception
+ doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(runnable2).run();
+
+ // nothing should have been canceled yet
+ verify(runnable1, never()).run();
+ verify(runnable2, never()).run();
+ verify(runnable3, never()).run();
+
+ assertTrue(mgr.isRunning());
+
+ // stop the controller
+ mgr.stop();
+
+ // all controllers should now be stopped
+ assertFalse(mgr.isRunning());
+
+ // everything should have been invoked
+ verify(runnable1).run();
+ verify(runnable2).run();
+ verify(runnable3).run();
+
+ // re-invoking stop should have no effect on the listeners
+ mgr.stop();
+
+ verify(runnable1).run();
+ verify(runnable2).run();
+ verify(runnable3).run();
+ }
+
+ @Test
+ public void testAdd() {
+ // still running - this should not be invoked
+ mgr.add(runnable1);
+ verify(runnable1, never()).run();
+
+ mgr.stop();
+
+ verify(runnable1).run();
+
+ // new additions should be invoked immediately
+ mgr.add(runnable2);
+ verify(runnable2).run();
+
+ // should work with exceptions, too
+ doThrow(new IllegalStateException(EXPECTED_EXCEPTION)).when(runnable3).run();
+ mgr.add(runnable3);
+ }
+
+ @Test
+ public void testRemove() {
+ mgr.add(runnable1);
+ mgr.add(runnable2);
+
+ verify(runnable1, never()).run();
+ verify(runnable2, never()).run();
+
+ // remove the second
+ mgr.remove(runnable2);
+
+ // should be able to remove it again
+ mgr.remove(runnable2);
+
+ mgr.stop();
+
+ // first should have run, but not the second
+ verify(runnable1).run();
+
+ verify(runnable2, never()).run();
+ }
+}
diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
new file mode 100644
index 000000000..b421c1ce2
--- /dev/null
+++ b/models-interactions/model-actors/actorServiceProvider/src/test/java/org/onap/policy/controlloop/actorserviceprovider/pipeline/PipelineControllerFutureTest.java
@@ -0,0 +1,254 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.controlloop.actorserviceprovider.pipeline;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class PipelineControllerFutureTest {
+ private static final IllegalStateException EXPECTED_EXCEPTION = new IllegalStateException("expected exception");
+ private static final String TEXT = "some text";
+
+ @Mock
+ private Runnable runnable1;
+
+ @Mock
+ private Runnable runnable2;
+
+ @Mock
+ private Future<String> future1;
+
+ @Mock
+ private Future<String> future2;
+
+ @Mock
+ private CompletableFuture<String> compFuture;
+
+
+ private PipelineControllerFuture<String> controller;
+
+
+ /**
+ * Initializes fields, including {@link #controller}. Adds all runners and futures to
+ * the controller.
+ */
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ controller = new PipelineControllerFuture<>();
+
+ controller.add(runnable1);
+ controller.add(future1);
+ controller.add(runnable2);
+ controller.add(future2);
+ }
+
+ @Test
+ public void testCancel_testAddFutureOfFBoolean_testAddRunnable__testIsRunning() {
+ assertTrue(controller.isRunning());
+
+ assertTrue(controller.cancel(false));
+
+ assertTrue(controller.isCancelled());
+ assertFalse(controller.isRunning());
+
+ verify(runnable1).run();
+ verify(runnable2).run();
+ verify(future1).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+
+ // re-invoke; nothing should change
+ assertTrue(controller.cancel(true));
+
+ assertTrue(controller.isCancelled());
+ assertFalse(controller.isRunning());
+
+ verify(runnable1).run();
+ verify(runnable2).run();
+ verify(future1).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+ }
+
+ @Test
+ public void testDelayedComplete() throws Exception {
+ controller.add(runnable1);
+
+ BiConsumer<String, Throwable> stopper = controller.delayedComplete();
+
+ // shouldn't have run yet
+ assertTrue(controller.isRunning());
+ verify(runnable1, never()).run();
+
+ stopper.accept(TEXT, null);
+
+ assertTrue(controller.isDone());
+ assertEquals(TEXT, controller.get());
+
+ assertFalse(controller.isRunning());
+ verify(runnable1).run();
+
+ // re-invoke; nothing should change
+ stopper.accept(TEXT, EXPECTED_EXCEPTION);
+ assertFalse(controller.isCompletedExceptionally());
+
+ assertFalse(controller.isRunning());
+ verify(runnable1).run();
+ }
+
+ /**
+ * Tests delayedComplete() when an exception is generated.
+ */
+ @Test
+ public void testDelayedCompleteWithException() throws Exception {
+ controller.add(runnable1);
+
+ BiConsumer<String, Throwable> stopper = controller.delayedComplete();
+
+ // shouldn't have run yet
+ assertTrue(controller.isRunning());
+ verify(runnable1, never()).run();
+
+ stopper.accept(TEXT, EXPECTED_EXCEPTION);
+
+ assertTrue(controller.isDone());
+ assertThatThrownBy(() -> controller.get()).hasCause(EXPECTED_EXCEPTION);
+
+ assertFalse(controller.isRunning());
+ verify(runnable1).run();
+
+ // re-invoke; nothing should change
+ stopper.accept(TEXT, null);
+ assertTrue(controller.isCompletedExceptionally());
+
+ assertFalse(controller.isRunning());
+ verify(runnable1).run();
+ }
+
+ @Test
+ public void testDelayedRemoveFutureOfF() throws Exception {
+ BiConsumer<String, Throwable> remover = controller.delayedRemove(future1);
+
+ remover.accept(TEXT, EXPECTED_EXCEPTION);
+
+ // should not have completed the controller
+ assertFalse(controller.isDone());
+
+ verify(future1, never()).cancel(anyBoolean());
+
+ controller.delayedComplete().accept(TEXT, EXPECTED_EXCEPTION);
+
+ verify(future1, never()).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+ }
+
+ @Test
+ public void testDelayedRemoveRunnable() throws Exception {
+ BiConsumer<String, Throwable> remover = controller.delayedRemove(runnable1);
+
+ remover.accept(TEXT, EXPECTED_EXCEPTION);
+
+ // should not have completed the controller
+ assertFalse(controller.isDone());
+
+ verify(runnable1, never()).run();
+
+ controller.delayedComplete().accept(TEXT, EXPECTED_EXCEPTION);
+
+ verify(runnable1, never()).run();
+ verify(runnable2).run();
+ }
+
+ @Test
+ public void testRemoveFutureOfF_testRemoveRunnable() {
+ controller.remove(runnable2);
+ controller.remove(future1);
+
+ controller.cancel(true);
+
+ verify(runnable1).run();
+ verify(runnable2, never()).run();
+ verify(future1, never()).cancel(anyBoolean());
+ verify(future2).cancel(anyBoolean());
+ }
+
+ @Test
+ public void testAddFunction() {
+ AtomicReference<String> value = new AtomicReference<>();
+
+ Function<String, CompletableFuture<String>> func = controller.add(input -> {
+ value.set(input);
+ return compFuture;
+ });
+
+ assertSame(compFuture, func.apply(TEXT));
+ assertEquals(TEXT, value.get());
+
+ verify(compFuture, never()).cancel(anyBoolean());
+
+ // should not have completed the controller
+ assertFalse(controller.isDone());
+
+ // cancel - should propagate
+ controller.cancel(false);
+
+ verify(compFuture).cancel(anyBoolean());
+ }
+
+ /**
+ * Tests add(Function) when the controller is not running.
+ */
+ @Test
+ public void testAddFunctionNotRunning() {
+ AtomicReference<String> value = new AtomicReference<>();
+
+ Function<String, CompletableFuture<String>> func = controller.add(input -> {
+ value.set(input);
+ return compFuture;
+ });
+
+ controller.cancel(false);
+
+ CompletableFuture<String> fut = func.apply(TEXT);
+ assertNotSame(compFuture, fut);
+ assertFalse(fut.isDone());
+
+ assertNull(value.get());
+ }
+}