This is an automated email from the ASF dual-hosted git repository. sruehl pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git
The following commit(s) were added to refs/heads/master by this push: new 292474b improved AdsAbstractPlcConnectionTest and refined Junit5Backport method. 292474b is described below commit 292474b2e60186f813346162d87764bd83e24c56 Author: Sebastian Rühl <sru...@apache.org> AuthorDate: Wed May 30 10:45:41 2018 +0200 improved AdsAbstractPlcConnectionTest and refined Junit5Backport method. --- .../ads/connection/AdsAbstractPlcConnection.java | 7 + .../connection/AdsAbstractPlcConnectionTest.java | 200 ++++++++++++++++++--- .../apache/plc4x/java/ads/util/Junit5Backport.java | 2 +- 3 files changed, 187 insertions(+), 22 deletions(-) diff --git a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnection.java b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnection.java index 5e0d647..2304199 100644 --- a/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnection.java +++ b/plc4j/protocols/ads/src/main/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnection.java @@ -204,6 +204,13 @@ public abstract class AdsAbstractPlcConnection extends AbstractPlcConnection imp super.close(); } + /** + * Clears the addressMapping. + */ + public void clearMapping() { + addressMapping.clear(); + } + protected <T> T getFromFuture(CompletableFuture<T> future, long timeout) { try { return future.get(timeout, TimeUnit.MILLISECONDS); diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnectionTest.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnectionTest.java index de5c77d..8b274fa 100644 --- a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnectionTest.java +++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/connection/AdsAbstractPlcConnectionTest.java @@ -21,12 +21,17 @@ package org.apache.plc4x.java.ads.connection; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; +import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.plc4x.java.ads.api.commands.AdsReadWriteResponse; import org.apache.plc4x.java.ads.api.commands.types.Data; import org.apache.plc4x.java.ads.api.commands.types.Result; import org.apache.plc4x.java.ads.api.generic.types.AmsNetId; import org.apache.plc4x.java.ads.api.generic.types.AmsPort; +import org.apache.plc4x.java.ads.model.AdsAddress; import org.apache.plc4x.java.ads.model.SymbolicAdsAddress; +import org.apache.plc4x.java.api.exceptions.PlcRuntimeException; import org.apache.plc4x.java.api.messages.*; import org.apache.plc4x.java.api.messages.specific.TypeSafePlcReadRequest; import org.apache.plc4x.java.api.messages.specific.TypeSafePlcReadResponse; @@ -36,20 +41,38 @@ import org.apache.plc4x.java.api.model.Address; import org.apache.plc4x.java.base.connection.ChannelFactory; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; +import java.util.Map; +import java.util.Queue; +import java.util.concurrent.*; -import static org.junit.Assert.assertNotNull; +import static org.apache.plc4x.java.ads.util.Junit5Backport.assertThrows; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; +@RunWith(MockitoJUnitRunner.class) +@SuppressWarnings("unchecked") public class AdsAbstractPlcConnectionTest { + private static final Logger LOGGER = LoggerFactory.getLogger(AdsAbstractPlcConnectionTest.class); + private AdsAbstractPlcConnection SUT; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private ChannelFactory channelFactory; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Channel channel; + @Before public void setUp() throws Exception { - ChannelFactory channelFactory = mock(ChannelFactory.class, RETURNS_DEEP_STUBS); SUT = new AdsAbstractPlcConnection(channelFactory, mock(AmsNetId.class), mock(AmsPort.class), mock(AmsNetId.class), mock(AmsPort.class)) { @Override protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) { @@ -57,24 +80,24 @@ public class AdsAbstractPlcConnectionTest { } }; - // Specific to mapAddress - Channel channel = mock(Channel.class, RETURNS_DEEP_STUBS); when(channelFactory.createChannel(any())).thenReturn(channel); - when(channel.writeAndFlush(any(PlcRequestContainer.class))).then(invocation -> { - PlcRequestContainer plcRequestContainer = invocation.getArgument(0); - PlcProprietaryResponse plcProprietaryResponse = mock(PlcProprietaryResponse.class, RETURNS_DEEP_STUBS); - AdsReadWriteResponse adsReadWriteResponse = mock(AdsReadWriteResponse.class, RETURNS_DEEP_STUBS); - when(adsReadWriteResponse.getResult()).thenReturn(Result.of(0)); - when(adsReadWriteResponse.getData()).thenReturn(Data.of(new byte[]{1, 2, 3, 4})); - when(plcProprietaryResponse.getResponse()).thenReturn(adsReadWriteResponse); - plcRequestContainer.getResponseFuture().complete(plcProprietaryResponse); - return mock(ChannelFuture.class); - }); SUT.connect(); } @Test + public void lazyConstructor() { + AdsAbstractPlcConnection constructed = new AdsAbstractPlcConnection(channelFactory, mock(AmsNetId.class), mock(AmsPort.class)) { + @Override + protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) { + return null; + } + }; + assertEquals(AdsAbstractPlcConnection.generateAMSNetId(), constructed.getSourceAmsNetId()); + assertEquals(AdsAbstractPlcConnection.generateAMSPort(), constructed.getSourceAmsPort()); + } + + @Test public void getTargetAmsNetId() { AmsNetId targetAmsNetId = SUT.getTargetAmsNetId(); assertNotNull(targetAmsNetId); @@ -112,6 +135,9 @@ public class AdsAbstractPlcConnectionTest { assertNotNull(read); CompletableFuture<TypeSafePlcReadResponse<Object>> typeSafeRead = SUT.read(mock(TypeSafePlcReadRequest.class)); assertNotNull(typeSafeRead); + + simulatePipelineError(() -> SUT.read(mock(PlcReadRequest.class))); + simulatePipelineError(() -> SUT.read(mock(TypeSafePlcReadRequest.class))); } @Test @@ -120,12 +146,43 @@ public class AdsAbstractPlcConnectionTest { assertNotNull(write); CompletableFuture<TypeSafePlcWriteResponse<Object>> typeSafeWrite = SUT.write(mock(TypeSafePlcWriteRequest.class)); assertNotNull(typeSafeWrite); + + simulatePipelineError(() -> SUT.write(mock(PlcWriteRequest.class))); + simulatePipelineError(() -> SUT.write(mock(TypeSafePlcWriteRequest.class))); } @Test public void send() { CompletableFuture send = SUT.send(mock(PlcProprietaryRequest.class)); assertNotNull(send); + + simulatePipelineError(() -> SUT.send(mock(PlcProprietaryRequest.class))); + } + + public void simulatePipelineError(FutureProducingTestRunnable futureProducingTestRunnable) { + ChannelFuture channelFuture = mock(ChannelFuture.class); + // Simulate error in the pipeline + when(channelFuture.addListener(any())).thenAnswer(invocation -> { + Future future = mock(Future.class); + when(future.isSuccess()).thenReturn(false); + when(future.cause()).thenReturn(new DummyException()); + GenericFutureListener genericFutureListener = invocation.getArgument(0); + genericFutureListener.operationComplete(future); + return mock(ChannelFuture.class); + }); + when(channel.writeAndFlush(any())).thenReturn(channelFuture); + assertThrows(DummyException.class, () -> { + CompletableFuture completableFuture = futureProducingTestRunnable.run(); + try { + completableFuture.get(3, TimeUnit.SECONDS); + fail("Should have thrown a ExecutionException"); + } catch (ExecutionException e) { + if (e.getCause() instanceof DummyException) { + throw (DummyException) e.getCause(); + } + throw e; + } + }); } @Test @@ -135,12 +192,47 @@ public class AdsAbstractPlcConnectionTest { @Test public void mapAddress() { - SUT.mapAddress(SymbolicAdsAddress.of("Main.byByte[0]")); + // positive + { + when(channel.writeAndFlush(any(PlcRequestContainer.class))).then(invocation -> { + PlcRequestContainer plcRequestContainer = invocation.getArgument(0); + PlcProprietaryResponse plcProprietaryResponse = mock(PlcProprietaryResponse.class, RETURNS_DEEP_STUBS); + AdsReadWriteResponse adsReadWriteResponse = mock(AdsReadWriteResponse.class, RETURNS_DEEP_STUBS); + when(adsReadWriteResponse.getResult()).thenReturn(Result.of(0)); + when(adsReadWriteResponse.getData()).thenReturn(Data.of(new byte[]{1, 2, 3, 4})); + when(plcProprietaryResponse.getResponse()).thenReturn(adsReadWriteResponse); + plcRequestContainer.getResponseFuture().complete(plcProprietaryResponse); + return mock(ChannelFuture.class); + }); + + SUT.mapAddress(SymbolicAdsAddress.of("Main.byByte[0]")); + SUT.mapAddress(SymbolicAdsAddress.of("Main.byByte[0]")); + verify(channel, times(1)).writeAndFlush(any(PlcRequestContainer.class)); + SUT.clearMapping(); + reset(channel); + } + // negative + { + when(channel.writeAndFlush(any(PlcRequestContainer.class))).then(invocation -> { + PlcRequestContainer plcRequestContainer = invocation.getArgument(0); + PlcProprietaryResponse plcProprietaryResponse = mock(PlcProprietaryResponse.class, RETURNS_DEEP_STUBS); + AdsReadWriteResponse adsReadWriteResponse = mock(AdsReadWriteResponse.class, RETURNS_DEEP_STUBS); + when(adsReadWriteResponse.getResult()).thenReturn(Result.of(1)); + when(plcProprietaryResponse.getResponse()).thenReturn(adsReadWriteResponse); + plcRequestContainer.getResponseFuture().complete(plcProprietaryResponse); + return mock(ChannelFuture.class); + }); + + assertThrows(PlcRuntimeException.class, () -> SUT.mapAddress(SymbolicAdsAddress.of("Main.byByte[0]"))); + verify(channel, times(1)).writeAndFlush(any(PlcRequestContainer.class)); + SUT.clearMapping(); + reset(channel); + } } @Test public void generateAMSNetId() { - AmsNetId targetAmsNetId = SUT.getTargetAmsNetId(); + AmsNetId targetAmsNetId = AdsAbstractPlcConnection.generateAMSNetId(); assertNotNull(targetAmsNetId); } @@ -151,14 +243,60 @@ public class AdsAbstractPlcConnectionTest { } @Test - public void close() { + public void close() throws Exception { + Map addressMapping = (Map) FieldUtils.getDeclaredField(AdsAbstractPlcConnection.class, "addressMapping", true).get(SUT); + addressMapping.put(mock(SymbolicAdsAddress.class), mock(AdsAddress.class)); SUT.close(); } @Test - public void getFromFuture() { - Object fromFuture = SUT.getFromFuture(mock(CompletableFuture.class, RETURNS_DEEP_STUBS), 1); - assertNotNull(fromFuture); + public void getFromFuture() throws Exception { + runInThread(() -> { + CompletableFuture completableFuture = mock(CompletableFuture.class, RETURNS_DEEP_STUBS); + Object fromFuture = SUT.getFromFuture(completableFuture, 1); + assertNotNull(fromFuture); + }); + runInThread(() -> { + CompletableFuture completableFuture = mock(CompletableFuture.class, RETURNS_DEEP_STUBS); + when(completableFuture.get(anyLong(), any())).thenThrow(InterruptedException.class); + assertThrows(PlcRuntimeException.class, () -> SUT.getFromFuture(completableFuture, 1)); + }); + runInThread(() -> { + CompletableFuture completableFuture = mock(CompletableFuture.class, RETURNS_DEEP_STUBS); + when(completableFuture.get(anyLong(), any())).thenThrow(ExecutionException.class); + assertThrows(PlcRuntimeException.class, () -> SUT.getFromFuture(completableFuture, 1)); + }); + runInThread(() -> { + CompletableFuture completableFuture = mock(CompletableFuture.class, RETURNS_DEEP_STUBS); + when(completableFuture.get(anyLong(), any())).thenThrow(TimeoutException.class); + assertThrows(PlcRuntimeException.class, () -> SUT.getFromFuture(completableFuture, 1)); + }); + assertFalse("The current Thread should not be interrupted", Thread.currentThread().isInterrupted()); + } + + /** + * Runs tests steps in a dedicated {@link Thread} so a possible {@link InterruptedException} doesn't lead to a + * interrupt flag being set on the main Thread ({@see Thread.currentThread().isInterrupted()}). + * + * @param testRunnable a special {@link Runnable} which adds a {@code throws Exception} to the {@code run} signature. + * @throws InterruptedException when this {@link Thread} gets interrupted. + */ + public void runInThread(TestRunnable testRunnable) throws InterruptedException { + Thread thread = new Thread(() -> { + try { + testRunnable.run(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + Queue<Throwable> uncaughtExceptions = new ConcurrentLinkedQueue<>(); + thread.setUncaughtExceptionHandler((t, e) -> uncaughtExceptions.add(e)); + thread.start(); + thread.join(); + if (!uncaughtExceptions.isEmpty()) { + uncaughtExceptions.forEach(throwable -> LOGGER.error("Assertion Error: Unexpected Exception", throwable)); + throw new AssertionError("Test failures. Check log"); + } } @Test @@ -166,4 +304,24 @@ public class AdsAbstractPlcConnectionTest { String s = SUT.toString(); assertNotNull(s); } + + /** + * Variant of {@link Runnable} which adds a {@code throws Exception} to the {@code run} signature. + */ + private interface TestRunnable { + /** + * @throws Exception when the test throws a exception. + * @see Runnable#run() + */ + void run() throws Exception; + } + + private static class DummyException extends Exception { + + } + + @FunctionalInterface + private interface FutureProducingTestRunnable { + CompletableFuture run() throws Exception; + } } \ No newline at end of file diff --git a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/util/Junit5Backport.java b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/util/Junit5Backport.java index 34d8783..973c351 100644 --- a/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/util/Junit5Backport.java +++ b/plc4j/protocols/ads/src/test/java/org/apache/plc4x/java/ads/util/Junit5Backport.java @@ -25,7 +25,7 @@ public class Junit5Backport { acceptor.accept(); } catch (Exception e) { if (!exception.isAssignableFrom(e.getClass())) { - throw new RuntimeException(e); + throw new AssertionError("Unexpected exception type " + e.getClass() + ". Expected " + exception, e); } } } -- To stop receiving notification emails like this one, please contact sru...@apache.org.