diff options
Diffstat (limited to 'models-interactions/model-actors/actorServiceProvider/src/test')
18 files changed, 3500 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()); + } +} diff --git a/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml new file mode 100644 index 000000000..f8a1e5112 --- /dev/null +++ b/models-interactions/model-actors/actorServiceProvider/src/test/resources/logback-test.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============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========================================================= +--> + +<configuration> + + <contextName>Actors</contextName> + <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" /> + + <!-- USE FOR STD OUT ONLY --> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <Pattern>%d %level %msg%n</Pattern> + </encoder> + </appender> + + <root level="warn"> + <appender-ref ref="STDOUT" /> + </root> + + <!-- this is just an example --> + <logger name="ch.qos.logback.classic" level="off" additivity="false"> + <appender-ref ref="STDOUT" /> + </logger> +</configuration> |