All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.californium.plugtests.TestClientAbstract Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2020 Bosch.IO GmbH and others.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * and Eclipse Distribution License v1.0 which accompany this distribution.
 * 
 * The Eclipse Public License is available at
 *    http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *    http://www.eclipse.org/org/documents/edl-v10.html.
 * 
 * Contributors:
 *    Bosch.IO GmbH - initial creation
 ******************************************************************************/
package org.eclipse.californium.plugtests;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.WebLink;
import org.eclipse.californium.core.coap.BlockOption;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.LinkFormat;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.MessageObserverAdapter;
import org.eclipse.californium.core.coap.Option;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.coap.Token;
import org.eclipse.californium.core.coap.option.OptionDefinition;
import org.eclipse.californium.core.coap.option.StandardOptionRegistry;
import org.eclipse.californium.core.coap.CoAP.Type;
import org.eclipse.californium.core.coap.EndpointContextTracer;
import org.eclipse.californium.core.network.Endpoint;
import org.eclipse.californium.core.network.EndpointManager;
import org.eclipse.californium.core.observe.NotificationListener;
import org.eclipse.californium.elements.EndpointContext;
import org.eclipse.californium.elements.util.StringUtil;

/**
 * Abstract class to support various test client implementations.
 */
public abstract class TestClientAbstract {

	private static final EndpointContextTracer ENDPOINT_CONTEXT_TRACER = new EndpointContextTracer() {
		@Override
		protected void onContextChanged(EndpointContext endpointContext) {
			System.out.println(Utils.prettyPrint(endpointContext));
		}
	};

	protected Report report = new Report();

	private Throwable sendError;
	protected Semaphore terminated = new Semaphore(0);

	/** The test name. */
	protected String testName = null;

	/** The verbose. */
	protected boolean verbose = true;

	/**
	 * Use synchronous or asynchronous requests. Sync recommended due to single
	 * threaded servers and slow resources.
	 */
	protected boolean sync = true;

	protected boolean useTcp = false;

	/**
	 * Current started observation.
	 */
	protected volatile Request observe;

	/**
	 * Last received notification.
	 * 
	 * Replaces deprecated notification processing with
	 * {@link Request#waitForResponse(long)}.
	 * 
	 * @see #waitForNotification(long)
	 * @see #startObserve(Request)
	 * @see #stopObservation()
	 */
	protected AtomicReference notification = new AtomicReference();

	/**
	 * Notification listener forwarding notifications to
	 * {@link #waitForNotification(long)}.
	 */
	protected TestNotificationListener listener;

	/**
	 * Instantiates a new test client abstract.
	 * 
	 * @param testName    the test name
	 * @param verbose     the verbose
	 * @param synchronous use synchronous or asynchronous requests
	 */
	public TestClientAbstract(String testName, boolean verbose, boolean synchronous) {
		if (testName == null || testName.isEmpty()) {
			throw new IllegalArgumentException("testName == null || testName.isEmpty()");
		}

		this.testName = testName;
		this.verbose = verbose;
		this.sync = synchronous;
	}

	/**
	 * Instantiates a new test client abstract.
	 * 
	 * @param testName the test name
	 */
	public TestClientAbstract(String testName) {
		this(testName, PlugtestChecker.verbose, true);
	}

	public void setUseTcp(String scheme) {
		useTcp = CoAP.isTcpScheme(scheme);
	}

	/**
	 * Start observe.
	 * 
	 * @param request request to start observation.
	 */
	protected void startObserve(Request request) {
		stopObservation();
		addContextObserver(request);
		Endpoint endpoint = EndpointManager.getEndpointManager().getDefaultEndpoint(request.getScheme());
		listener = new TestNotificationListener(endpoint);
		endpoint.addNotificationListener(listener);
		observe = request;
		request.send(endpoint);
	}

	/**
	 * Wait for notification.
	 * 
	 * @param timeout timeout in milliseconds
	 * @return response, or {@code null}, if no response is received within the
	 *         provided timeout.
	 * @throws IllegalStateException if the observation was not started calling
	 *                               {@link #startObserve(Request)}
	 * @throws InterruptedException  if thread was interrupted during wait.
	 */
	protected Response waitForNotification(long timeout) throws IllegalStateException, InterruptedException {
		if (listener == null) {
			throw new IllegalStateException("missing startObserve");
		}
		Response notify = null;
		synchronized (notification) {
			notify = notification.get();
			if (notify == null) {
				notification.wait(timeout);
				notify = notification.get();
			}
			notification.set(null);
		}
		return notify;
	}

	/**
	 * Stop observation and free resources.
	 */
	protected void stopObservation() {
		if (listener != null) {
			listener.close();
			listener = null;
			observe = null;
		}
	}

	/**
	 * Execute request.
	 * 
	 * @param request     the request
	 * @param serverURI   the server uri
	 * @param resourceUri the resource uri
	 */
	protected void executeRequest(Request request, String serverURI, String resourceUri) {

		// defensive check for slash
		if (!serverURI.endsWith("/") && !resourceUri.startsWith("/")) {
			resourceUri = "/" + resourceUri;
		}

		URI uri = null;
		try {
			uri = new URI(serverURI + resourceUri);
			useTcp = CoAP.isTcpScheme(uri.getScheme());
		} catch (URISyntaxException use) {
			System.err.println("Invalid URI: " + use.getMessage());
		}

		addContextObserver(request);

		request.setURI(uri);

		request.addMessageObserver(new TestResponseHandler(request));

		// print request info
		if (verbose) {
			System.out.println("Request for test " + this.testName + " sent");
			Utils.prettyPrint(request);
		}

		// execute the request
		try {
			request.send();
			if (sync) {
				request.waitForResponse(5000);
			}
		} catch (InterruptedException e) {
			System.err.println("Interupted during receive: " + e.getMessage());
			System.exit(-1);
		}
	}

	/**
	 * Add {@link #ENDPOINT_CONTEXT_TRACER} to request and set endpoint context, if
	 * not already available.
	 * 
	 * @param request request to add observer and set context
	 */
	protected static void addContextObserver(Request request) {
		EndpointContext context = ENDPOINT_CONTEXT_TRACER.getCurrentContext();
		if (context != null && request.getDestinationContext() == null) {
			request.setDestinationContext(context);
		}
		request.addMessageObserver(ENDPOINT_CONTEXT_TRACER);
	}

	public synchronized void addSummaryEntry(String entry) {
		report.addEntry(entry);
	}

	public Report getReport() {
		return report;
	}

	public void tickOffTest() {
		terminated.release();
	}

	public void waitForUntilTestHasTerminated() throws Throwable {
		try {
			terminated.acquire();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (sendError != null) {
			throw sendError;
		}
	}

	/**
	 * The Class TestResponseHandler.
	 */
	protected class TestResponseHandler extends MessageObserverAdapter {

		private Request request;
		private final AtomicInteger requestCounter = new AtomicInteger();

		public TestResponseHandler(Request request) {
			this.request = request;
		}

		@Override
		public void onRetransmission() {
			requestCounter.decrementAndGet();
		}

		@Override
		public void onSent(boolean retransmission) {
			requestCounter.incrementAndGet();
		}

		@Override
		public void onResponse(Response response) {
			System.out.println();
			System.out.println("**** TEST: " + testName + " ****");

			// checking the response
			if (response != null) {
				int requests = requestCounter.get();
				// print response info
				if (verbose) {
					System.out.println("Response received");
					System.out.println(
							"Time elapsed (ms): " + TimeUnit.NANOSECONDS.toMillis(response.getApplicationRttNanos()));
					if (requests > 1) {
						System.out.println(requests + " blocks");
					}
					Utils.prettyPrint(response);
				}
				if (response.getOptions().hasBlock1()) {
					requests -= response.getOptions().getBlock1().getNum();
				}
				if ((requests > 1) && !response.getOptions().hasBlock2()) {
					// set block2 option from counter
					// backwards compatibility (test only)
					int size = response.getPayloadSize() / requests;
					int bit = Integer.highestOneBit(size);
					if ((size - bit) != 0) {
						bit <<= 1;
					}
					response.getOptions().setBlock2(BlockOption.size2Szx(bit), false, requests - 1);
				}

				System.out.println("**** BEGIN CHECK ****");

				if (checkResponse(request, response)) {
					System.out.println("**** TEST PASSED ****");
					addSummaryEntry(testName + ": PASSED");
				} else {
					System.out.println("**** TEST FAILED ****");
					addSummaryEntry(testName + ": --FAILED--");
				}
				tickOffTest();
			}
		}

		@Override
		public void onSendError(Throwable error) {
			sendError = error;
			tickOffTest();
		}

		protected void failed() {
			tickOffTest();
		}
	}

	/**
	 * Notification listener forwarding notifies as response. Backwards
	 * compatibility to 1.0.0 notification implementation.
	 */
	protected class TestNotificationListener implements NotificationListener {

		private Endpoint endpoint;

		public TestNotificationListener(Endpoint endpoint) {
			this.endpoint = endpoint;
		}

		@Override
		public void onNotification(Request request, Response response) {
			Request origin = observe;
			if (origin != null && origin.getToken().equals(response.getToken())) {
				synchronized (notification) {
					notification.set(response);
					notification.notify();
				}
			}
		}

		public void close() {
			Request origin = observe;
			if (origin != null) {
				if (origin.isObserve()) {
					Request cancel = Request.newGet();
					cancel.setDestinationContext(origin.getDestinationContext());
					// use same Token
					cancel.setToken(origin.getToken());
					// copy options
					cancel.setOptions(origin.getOptions());
					// set Observe to cancel
					cancel.setObserveCancel();
					endpoint.sendRequest(cancel);
					try {
						cancel.waitForResponse(2000);
					} catch (InterruptedException e) {
					}
				}
				endpoint.cancelObservation(origin.getToken());
			}
			endpoint.removeNotificationListener(this);
		}

	}

	/**
	 * Check response.
	 * 
	 * @param request  the request
	 * @param response the response
	 * @return true, if successful
	 */
	protected abstract boolean checkResponse(Request request, Response response);

	/**
	 * Check int.
	 * 
	 * @param expected  the expected
	 * @param actual    the actual
	 * @param fieldName the field name
	 * @return true, if successful
	 */
	protected boolean checkInt(int expected, int actual, String fieldName) {
		boolean success = expected == actual;

		if (!success) {
			System.out.println("FAIL: Expected " + fieldName + ": " + expected + ", but was: " + actual);
		} else {
			System.out.println("PASS: Correct " + fieldName + String.format(" (%d)", actual));
		}

		return success;
	}

	/**
	 * Check int.
	 * 
	 * @param expected  the expected
	 * @param actual    the actual
	 * @param fieldName the field name
	 * @return true, if successful
	 */
	protected boolean checkInts(int[] expected, int actual, String fieldName) {
		boolean success = false;
		for (int i : expected) {
			if (i == actual) {
				success = true;
				break;
			}
		}

		if (!success) {
			System.out
					.println("FAIL: Expected " + fieldName + ": " + Arrays.toString(expected) + ", but was: " + actual);
		} else {
			System.out.println("PASS: Correct " + fieldName + String.format(" (%d)", actual));
		}

		return success;
	}

	/**
	 * Check code.
	 * 
	 * @param expected the expected code
	 * @param actual   the actual code
	 * @return true, if successful
	 */
	protected boolean checkCode(CoAP.ResponseCode expected, CoAP.ResponseCode actual) {
		boolean success = expected.equals(actual);

		if (!success) {
			System.out.println("FAIL: Expected code: " + expected + ", but was: " + actual);
		} else {
			System.out.println("PASS: Correct code: " + actual);
		}

		return success;
	}

	/**
	 * Check codes.
	 * 
	 * @param expected the expected codec
	 * @param actual   the actual code
	 * @return true, if successful
	 */
	protected boolean checkCodes(CoAP.ResponseCode[] expected, CoAP.ResponseCode actual) {
		boolean success = false;
		for (CoAP.ResponseCode code : expected) {
			if (code.equals(actual)) {
				success = true;
				break;
			}
		}

		if (!success) {
			System.out.println("FAIL: Expected code: " + Arrays.toString(expected) + ", but was: " + actual);
		} else {
			System.out.println("PASS: Correct code: " + actual);
		}

		return success;
	}

	/**
	 * Check String.
	 * 
	 * @param expected  the expected
	 * @param actual    the actual
	 * @param fieldName the field name
	 * @return true, if successful
	 */
	protected boolean checkString(String expected, String actual, String fieldName) {
		boolean success = expected.equals(actual);

		if (!success) {
			System.out.println("FAIL: Expected " + fieldName + ": \"" + expected + "\", but was: \"" + actual + "\"");
		} else {
			System.out.println("PASS: Correct " + fieldName + " \"" + actual + "\"");
		}

		return success;
	}

	/**
	 * Check type.
	 * 
	 * @param expectedMessageType the expected message type
	 * @param actualMessageType   the actual message type
	 * @return true, if successful
	 */
	protected boolean checkType(Type expectedMessageType, Type actualMessageType) {
		if (useTcp) {
			// TCP doesn't sue a message type!
			return true;
		}
		boolean success = expectedMessageType.equals(actualMessageType);

		if (!success) {
			System.out.printf("FAIL: Expected type %s, but was %s\n", expectedMessageType, actualMessageType);
		} else {
			System.out.printf("PASS: Correct type (%s)\n", actualMessageType.toString());
		}

		return success;
	}

	/**
	 * Check types.
	 * 
	 * @param expectedMessageTypes the expected message types
	 * @param actualMessageType    the actual message type
	 * @return true, if successful
	 */
	protected boolean checkTypes(Type[] expectedMessageTypes, Type actualMessageType) {
		boolean success = false;
		for (Type messageType : expectedMessageTypes) {
			if (messageType.equals(actualMessageType)) {
				success = true;
				break;
			}
		}

		if (!success) {
			StringBuilder sb = new StringBuilder();
			for (Type messageType : expectedMessageTypes) {
				sb.append(", ").append(messageType);
			}
			sb.delete(0, 2); // delete the first ", "

			System.out.printf("FAIL: Expected type %s, but was %s\n", "[ " + sb.toString() + " ]", actualMessageType);
		} else {
			System.out.printf("PASS: Correct type (%s)\n", actualMessageType.toString());
		}

		return success;
	}

	/**
	 * Checks for Content-Type option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasContentType(Response response) {
		boolean success = response.getOptions().hasContentFormat() || response.getPayloadSize() == 0
				|| !response.isSuccess();

		if (!success) {
			System.out.println("FAIL: Response without Content-Type");
		} else {
			System.out.printf("PASS: Content-Type (%s)\n",
					MediaTypeRegistry.toString(response.getOptions().getContentFormat()));
		}

		return success;
	}

	/**
	 * Checks for Location-Path option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasLocation(Response response) {
		// boolean success =
		// response.hasOption(OptionNumberRegistry.LOCATION_PATH);
		boolean success = response.getOptions().getLocationPathCount() > 0;

		if (!success) {
			System.out.println("FAIL: Response without Location");
		} else {
			System.out.printf("PASS: Location (%s)\n", response.getOptions().getLocationPathString());
		}

		return success;
	}

	/**
	 * Checks for ETag option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasEtag(Response response) {
		// boolean success = response.hasOption(OptionNumberRegistry.ETAG);
		boolean success = response.getOptions().getETagCount() > 0;

		if (!success) {
			System.out.println("FAIL: Response without Etag");
		} else {
			System.out.printf("PASS: Etag (%s)\n", Utils.toHexString(response.getOptions().getETags().get(0)));
		}

		return success;
	}

	/**
	 * Checks for not empty payload.
	 * 
	 * @param response the response
	 * @return true, if not empty payload
	 */
	protected boolean hasNonEmptyPayload(Response response) {
		boolean success = response.getPayloadSize() > 0;
		boolean print = MediaTypeRegistry.isPrintable(response.getOptions().getContentFormat());

		if (!success) {
			System.out.println("FAIL: Response with empty payload");
		} else if (print) {
			String payload = response.getPayloadString();
			if (payload.length() < 50) {
				System.out.printf("PASS: Payload not empty \"%s\"%n", response.getPayloadString());
			} else {
				System.out.printf("PASS: Payload not empty%n%s%n", response.getPayloadString());
			}
		} else {
			System.out.printf("PASS: Payload not empty%n0x%s%n", StringUtil.byteArray2Hex(response.getPayload()));
		}

		return success;
	}

	/**
	 * Checks for Max-Age option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasMaxAge(Response response) {
		// boolean success =
		// response.hasOption(OptionNumberRegistry.MAX_AGE);
		boolean success = response.getOptions().hasMaxAge();

		if (!success) {
			System.out.println("FAIL: Response without Max-Age");
		} else {
			System.out.printf("PASS: Max-Age (%s)\n", response.getOptions().getMaxAge());
		}

		return success;
	}

	/**
	 * Checks for Location-Query option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasLocationQuery(Response response) {
		// boolean success =
		// response.hasOption(OptionNumberRegistry.LOCATION_QUERY);
		boolean success = response.getOptions().getLocationQueryCount() > 0;

		if (!success) {
			System.out.println("FAIL: Response without Location-Query");
		} else {
			System.out.printf("PASS: Location-Query (%s)\n", response.getOptions().getLocationQueryString());
		}

		return success;
	}

	/**
	 * Checks for Token option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasToken(Response response) {
		boolean success = response.getToken() != null;

		if (!success) {
			System.out.println("FAIL: Response without Token");
		} else {
			System.out.printf("PASS: Token (%s)\n", response.getTokenString());
		}

		return success;
	}

	/**
	 * Checks for absent Token option.
	 * 
	 * @param response the response
	 * @return true, if successful
	 */
	protected boolean hasNoToken(Response response) {
		boolean success = response.hasEmptyToken();

		if (!success) {
			System.out.println("FAIL: Expected no token but had " + response.getTokenString());
		} else {
			System.out.printf("PASS: No Token\n");
		}

		return success;
	}

	protected boolean hasObserve(Response response) {
		return hasOption(response, StandardOptionRegistry.OBSERVE, false);
	}

	protected boolean hasNoObserve(Response response) {
		return hasOption(response, StandardOptionRegistry.OBSERVE, true);
	}

	protected boolean hasOption(Response response, OptionDefinition optionDefintion, boolean invert) {
		String name = optionDefintion.getName();
		List




© 2015 - 2024 Weber Informatics LLC | Privacy Policy