From f9f8b9158305f97c4c7a339c84c1eb4799a5c254 Mon Sep 17 00:00:00 2001 From: Simon Bernard Date: Wed, 27 Jun 2018 16:22:13 +0200 Subject: [PATCH] #429: tests to check there is no leak when error occurred before sending --- .../core/test/CountingMessageObserver.java | 78 +++++++++++ .../californium/core/test/ErrorInjector.java | 126 ++++++++++++++++++ .../lockstep/BlockwiseClientSideTest.java | 79 +++++++++++ .../test/lockstep/ObserveClientSideTest.java | 83 +++++++++++- 4 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 californium-core/src/test/java/org/eclipse/californium/core/test/CountingMessageObserver.java create mode 100644 californium-core/src/test/java/org/eclipse/californium/core/test/ErrorInjector.java diff --git a/californium-core/src/test/java/org/eclipse/californium/core/test/CountingMessageObserver.java b/californium-core/src/test/java/org/eclipse/californium/core/test/CountingMessageObserver.java new file mode 100644 index 0000000000..cd87bbd88d --- /dev/null +++ b/californium-core/src/test/java/org/eclipse/californium/core/test/CountingMessageObserver.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2018 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial creation + ******************************************************************************/ +package org.eclipse.californium.core.test; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.californium.core.coap.MessageObserverAdapter; +import org.eclipse.californium.core.coap.Response; + +public class CountingMessageObserver extends MessageObserverAdapter { + + public AtomicInteger loadCalls = new AtomicInteger(); + public AtomicInteger errorCalls = new AtomicInteger(); + + @Override + public void onRetransmission() { + } + + @Override + public void onResponse(Response response) { + int counter; + synchronized (this) { + counter = loadCalls.incrementAndGet(); + notifyAll(); + } + System.out.println("Received " + counter + ". Notification: " + response); + } + + @Override + public void onSendError(Throwable error) { + int counter; + synchronized (this) { + counter = errorCalls.incrementAndGet(); + notifyAll(); + } + System.out.println(counter + " Errors!"); + } + + public boolean waitForLoadCalls(final int counter, final long timeout, final TimeUnit unit) + throws InterruptedException { + return waitForCalls(counter, timeout, unit, loadCalls); + } + + public boolean waitForErrorCalls(final int counter, final long timeout, final TimeUnit unit) + throws InterruptedException { + return waitForCalls(counter, timeout, unit, errorCalls); + } + + private synchronized boolean waitForCalls(final int counter, final long timeout, final TimeUnit unit, + AtomicInteger calls) throws InterruptedException { + if (0 < timeout) { + long end = System.nanoTime() + unit.toNanos(timeout); + while (calls.get() < counter) { + long left = TimeUnit.NANOSECONDS.toMillis(end - System.nanoTime()); + if (0 < left) { + wait(left); + } else { + break; + } + } + } + return calls.get() >= counter; + } +} diff --git a/californium-core/src/test/java/org/eclipse/californium/core/test/ErrorInjector.java b/californium-core/src/test/java/org/eclipse/californium/core/test/ErrorInjector.java new file mode 100644 index 0000000000..118eb45fbd --- /dev/null +++ b/californium-core/src/test/java/org/eclipse/californium/core/test/ErrorInjector.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2018 Sierra Wireless and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.html. + * + * Contributors: + * Sierra Wireless - initial creation + ******************************************************************************/ +package org.eclipse.californium.core.test; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.californium.core.coap.EmptyMessage; +import org.eclipse.californium.core.coap.MessageObserver; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.interceptors.MessageInterceptor; +import org.eclipse.californium.elements.EndpointContext; + +public class ErrorInjector implements MessageInterceptor, MessageObserver{ + + private AtomicBoolean errorOnEstablishedContext = new AtomicBoolean(false); + private AtomicBoolean errorOnSent = new AtomicBoolean(false); + private AtomicBoolean errorOnReadyToSend = new AtomicBoolean(false); + + + public void setErrorOnEstablishedContext() { + errorOnEstablishedContext.set(true); + } + + public void setErrorOnSent() { + errorOnSent.set(true); + } + + public void setErrorOnReadyToSend() { + errorOnReadyToSend.set(true); + } + + //************* Message Interceptor *******************// + + @Override + public void sendRequest(Request request) { + request.addMessageObserver(this); + } + + @Override + public void sendResponse(Response response) { + } + + @Override + public void sendEmptyMessage(EmptyMessage message) { + } + + @Override + public void receiveRequest(Request request) { + } + + @Override + public void receiveResponse(Response response) { + } + + @Override + public void receiveEmptyMessage(EmptyMessage message) { + } + + //************* Message observer *******************// + @Override + public void onRetransmission() { + } + + @Override + public void onResponse(Response response) { + } + + @Override + public void onAcknowledgement() { + } + + @Override + public void onReject() { + } + + @Override + public void onTimeout() { + } + + @Override + public void onCancel() { + } + + @Override + public void onReadyToSend() { + if (errorOnReadyToSend.getAndSet(false)) { + throw new IllegalStateException("Simulate error before to sent"); + } + } + + @Override + public void onSent() { + if (errorOnReadyToSend.getAndSet(false)) { + throw new IllegalStateException("Simulate error on sent"); + } + } + + @Override + public void onSendError(Throwable error) { + } + + @Override + public void onContextEstablished(EndpointContext endpointContext) { + if (errorOnEstablishedContext.getAndSet(false)) { + throw new IllegalStateException("Simulate error on context established"); + } + } + + @Override + public void onComplete() { + } +} diff --git a/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/BlockwiseClientSideTest.java b/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/BlockwiseClientSideTest.java index 380f4777b1..484ff17d70 100644 --- a/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/BlockwiseClientSideTest.java +++ b/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/BlockwiseClientSideTest.java @@ -56,6 +56,8 @@ import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.core.test.CountingMessageObserver; +import org.eclipse.californium.core.test.ErrorInjector; import org.eclipse.californium.core.test.MessageExchangeStoreTool.CoapTestEndpoint; import org.eclipse.californium.rule.CoapNetworkRule; import org.junit.After; @@ -1199,4 +1201,81 @@ public void testIncompleteBlock1AckNoResponse() throws Exception { printServerLog(clientInterceptor); } + + /** + * Verify there is no leak if we failed before to sent block1 request + */ + @Test + public void testBlock1FailureBeforeToSend() throws Exception { + System.out.println("blockwise PUT failed before to send a block1 request"); + reqtPayload = generateRandomPayload(300); + String path = "test"; + + // Add error injector to client endpoint to be able to simulate error + // before we really send a message. + ErrorInjector errorInjector = new ErrorInjector(); + client.addInterceptor(errorInjector); + + // Start block 1 transfer + Request request = createRequest(PUT, path, server); + request.setPayload(reqtPayload); + CountingMessageObserver counter = new CountingMessageObserver(); + request.addMessageObserver(counter); + client.sendRequest(request); + server.expectRequest(CON, PUT, path).storeBoth("A").block1(0, true, 128).size1(reqtPayload.length()) + .payload(reqtPayload.substring(0, 128)).go(); + server.sendResponse(ACK, CONTINUE).loadBoth("A").block1(0, true, 128).go(); + server.expectRequest(CON, PUT, path).storeBoth("B").block1(1, true, 128) + .payload(reqtPayload.substring(128, 256)).go(); + + // Simulate error before we send the next block1 request + errorInjector.setErrorOnReadyToSend(); + server.sendResponse(ACK, CONTINUE).loadBoth("B").block1(1, true, 128).go(); + + // Wait for error + counter.waitForErrorCalls(1, 1000, TimeUnit.MILLISECONDS); + + // We should get a error + assertEquals("An error is expected", 1, counter.errorCalls.get()); + + // @after check there is no leak + } + + /** + * Verify there is no leak if we failed before to sent block2 request + */ + @Test + public void testBlock2FailureBeforeToSend() throws Exception { + System.out.println("blockwise GET failed before to send a block2 request:"); + respPayload = generateRandomPayload(300); + String path = "test"; + + // Add error injector to client endpoint to be able to simulate error + // before we really send a message. + ErrorInjector errorInjector = new ErrorInjector(); + client.addInterceptor(errorInjector); + + // Start block 2 transfer + Request request = createRequest(GET, path, server); + CountingMessageObserver counter = new CountingMessageObserver(); + request.addMessageObserver(counter); + client.sendRequest(request); + server.expectRequest(CON, GET, path).storeBoth("A").go(); + server.sendResponse(ACK, CONTENT).loadBoth("A").block2(0, true, 128).size2(respPayload.length()) + .payload(respPayload.substring(0, 128)).go(); + server.expectRequest(CON, GET, path).storeBoth("B").block2(1, false, 128).go(); + + // Simulate error before we send the next block2 request + errorInjector.setErrorOnReadyToSend(); + server.sendResponse(ACK, CONTENT).loadBoth("B").block2(1, true, 128).payload(respPayload.substring(128, 256)) + .go(); + + // Wait for error + counter.waitForErrorCalls(1, 1000, TimeUnit.MILLISECONDS); + + // We should get a error + assertEquals("An error is expected", 1, counter.errorCalls.get()); + + // @after check there is no leak + } } diff --git a/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/ObserveClientSideTest.java b/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/ObserveClientSideTest.java index 9a3cc4b2d6..ac9f41474c 100644 --- a/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/ObserveClientSideTest.java +++ b/californium-core/src/test/java/org/eclipse/californium/core/test/lockstep/ObserveClientSideTest.java @@ -39,9 +39,7 @@ import static org.eclipse.californium.core.test.lockstep.IntegrationTestTools.*; import static org.eclipse.californium.core.test.MessageExchangeStoreTool.*; import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertNull; +import static org.junit.Assert.*; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -53,6 +51,8 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.interceptors.MessageTracer; +import org.eclipse.californium.core.test.CountingMessageObserver; +import org.eclipse.californium.core.test.ErrorInjector; import org.eclipse.californium.rule.CoapNetworkRule; import org.junit.After; import org.junit.AfterClass; @@ -1179,4 +1179,81 @@ public void testBlockwiseObserveAndTimedoutNotification() throws Exception { } + /** + * Verify there is no leak if we failed before to sent the CoAP request. + */ + @Test + public void testObserveFailureBeforeToSend() throws Exception { + System.out.println("Observe fails before we send request:"); + respPayload = generateRandomPayload(10); + String path = "test"; + + // Add error injector to client endpoint to be able to simulate error + // before we really send the message. + ErrorInjector errorInjector = new ErrorInjector(); + client.addInterceptor(errorInjector); + errorInjector.setErrorOnReadyToSend(); + + // Try to send request + Request request = createRequest(GET, path, server); + SynchronousNotificationListener notificationListener = new SynchronousNotificationListener(request); + client.addNotificationListener(notificationListener); + CountingMessageObserver counter = new CountingMessageObserver(); + request.addMessageObserver(counter); + request.setObserve(); + client.sendRequest(request); + + // Wait for error + counter.waitForErrorCalls(1, 1000, TimeUnit.MILLISECONDS); + + // We should get a error + assertEquals("An error is expected", 1, counter.errorCalls.get()); + + // @after check there is no leak + } + + /** + * Verify there is no leak if we failed before to sent block request for a notification. + */ + @Test + public void testObserveFailureBeforeToSendDuringBlockNotification() throws Exception { + System.out.println("Observe fails before we send the next block2 request for a notification"); + respPayload = generateRandomPayload(10); + String path = "test"; + + // Add error injector to client endpoint to be able to simulate error + // before we really send the message. + ErrorInjector errorInjector = new ErrorInjector(); + client.addInterceptor(errorInjector); + + // establish observe relation + Request request = createRequest(GET, path, server); + request.setObserve(); + SynchronousNotificationListener notificationListener = new SynchronousNotificationListener(request); + client.addNotificationListener(notificationListener); + respPayload = generateRandomPayload(16); + client.sendRequest(request); + server.expectRequest(CON, GET, path).storeBoth("OBS").go(); + server.sendResponse(ACK, CONTENT).loadBoth("OBS").observe(1).payload(respPayload).go(); + Response response = request.waitForResponse(2000); + assertResponseContainsExpectedPayload(response, respPayload); + + // New notification + String notifyPayload = generateRandomPayload(32); + server.sendResponse(CON, CONTENT).loadToken("OBS").observe(3).mid(++mid).block2(0, true, 16) + .payload(notifyPayload.substring(0, 16)).go(); + // Expect ACK and Next Block request for the notification + server.startMultiExpectation(); + server.expectEmpty(ACK, mid).go(); + server.expectRequest(CON, GET, path).storeBoth("BLOCK").block2(1, false, 16).go(); + server.goMultiExpectation(); + + // Simulate error before we send the next block2 request + errorInjector.setErrorOnReadyToSend(); + server.sendResponse(ACK, CONTENT).loadBoth("BLOCK").block2(1, true, 16).payload(notifyPayload.substring(16, 32)).go(); + + // TODO We would like to check if the block2 request failed but we have no API for a block2 request which failed to be sent + + // @after check there is no leak + } }