summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/utils/junit/src/main/java
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/utils/junit/src/main/java
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/utils/junit/src/main/java')
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java55
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ImmutableClassChecker.java168
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/IntegrationTest.java25
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/NullScheduledExecutor.java135
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestTools.java210
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestUtils.java194
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/UtilityClassChecker.java149
-rw-r--r--framework/src/onos/utils/junit/src/main/java/org/onlab/junit/package-info.java20
8 files changed, 956 insertions, 0 deletions
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java
new file mode 100644
index 00000000..09b3fe37
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ExceptionTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Base for exception tests.
+ */
+public abstract class ExceptionTest {
+
+ protected static final Throwable CAUSE = new RuntimeException("boom");
+ protected static final String MESSAGE = "Uh oh.... boom";
+
+ protected abstract Exception getDefault();
+ protected abstract Exception getWithMessage();
+ protected abstract Exception getWithMessageAndCause();
+
+ @Test
+ public void noMessageNoCause() {
+ Exception e = getDefault();
+ assertEquals("incorrect message", null, e.getMessage());
+ assertEquals("incorrect cause", null, e.getCause());
+ }
+
+ @Test
+ public void withMessage() {
+ Exception e = getWithMessage();
+ assertEquals("incorrect message", MESSAGE, e.getMessage());
+ assertEquals("incorrect cause", null, e.getCause());
+ }
+
+ @Test
+ public void withCause() {
+ Exception e = getWithMessageAndCause();
+ assertEquals("incorrect message", MESSAGE, e.getMessage());
+ assertSame("incorrect cause", CAUSE, e.getCause());
+ }
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ImmutableClassChecker.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ImmutableClassChecker.java
new file mode 100644
index 00000000..80aa2cb1
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/ImmutableClassChecker.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import org.hamcrest.Description;
+import org.hamcrest.StringDescription;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Hamcrest style class for verifying that a class follows the
+ * accepted rules for immutable classes.
+ *
+ * The rules that are enforced for immutable classes:
+ * - the class must be declared final
+ * - all data members of the class must be declared private and final
+ * - the class must not define any setter methods
+ */
+
+public class ImmutableClassChecker {
+
+ private String failureReason = "";
+
+ /**
+ * Method to determine if a given class is a properly specified
+ * immutable class.
+ *
+ * @param clazz the class to check
+ * @return true if the given class is a properly specified immutable class.
+ */
+ private boolean isImmutableClass(Class<?> clazz, boolean allowNonFinalClass) {
+ // class must be declared final
+ if (!allowNonFinalClass && !Modifier.isFinal(clazz.getModifiers())) {
+ failureReason = "a class that is not final";
+ return false;
+ }
+
+ // class must have only final and private data members
+ for (final Field field : clazz.getDeclaredFields()) {
+ if (field.getName().startsWith("_") ||
+ field.getName().startsWith("$")) {
+ // eclipse generated code may insert switch table - ignore
+ // cobertura sticks these fields into classes - ignore them
+ continue;
+ }
+ if (!Modifier.isFinal(field.getModifiers())) {
+ failureReason = "a field named '" + field.getName() +
+ "' that is not final";
+ return false;
+ }
+ if (!Modifier.isPrivate(field.getModifiers())) {
+ //
+ // NOTE: We relax the recommended rules for defining immutable
+ // objects and allow "static final" fields that are not
+ // private. The "final" check was already done above so we
+ // don't repeat it here.
+ //
+ if (!Modifier.isStatic(field.getModifiers())) {
+ failureReason = "a field named '" + field.getName() +
+ "' that is not private and is not static";
+ return false;
+ }
+ }
+ }
+
+ // class must not define any setters
+ for (final Method method : clazz.getMethods()) {
+ if (method.getDeclaringClass().equals(clazz)) {
+ if (method.getName().startsWith("set")) {
+ failureReason = "a class with a setter named '" + method.getName() + "'";
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Describe why an error was reported. Uses Hamcrest style Description
+ * interfaces.
+ *
+ * @param description the Description object to use for reporting the
+ * mismatch
+ */
+ public void describeMismatch(Description description) {
+ description.appendText(failureReason);
+ }
+
+ /**
+ * Describe the source object that caused an error, using a Hamcrest
+ * Matcher style interface. In this case, it always returns
+ * that we are looking for a properly defined utility class.
+ *
+ * @param description the Description object to use to report the "to"
+ * object
+ */
+ public void describeTo(Description description) {
+ description.appendText("a properly defined immutable class");
+ }
+
+ /**
+ * Assert that the given class adheres to the immutable class rules.
+ *
+ * @param clazz the class to check
+ *
+ * @throws java.lang.AssertionError if the class is not an
+ * immutable class
+ */
+ public static void assertThatClassIsImmutable(Class<?> clazz) {
+ final ImmutableClassChecker checker = new ImmutableClassChecker();
+ if (!checker.isImmutableClass(clazz, false)) {
+ final Description toDescription = new StringDescription();
+ final Description mismatchDescription = new StringDescription();
+
+ checker.describeTo(toDescription);
+ checker.describeMismatch(mismatchDescription);
+ final String reason =
+ "\n" +
+ "Expected: is \"" + toDescription.toString() + "\"\n" +
+ " but : was \"" + mismatchDescription.toString() + "\"";
+
+ throw new AssertionError(reason);
+ }
+ }
+
+ /**
+ * Assert that the given class adheres to the immutable class rules, but
+ * is not declared final. Classes that need to be inherited from cannot be
+ * declared final.
+ *
+ * @param clazz the class to check
+ *
+ * @throws java.lang.AssertionError if the class is not an
+ * immutable class
+ */
+ public static void assertThatClassIsImmutableBaseClass(Class<?> clazz) {
+ final ImmutableClassChecker checker = new ImmutableClassChecker();
+ if (!checker.isImmutableClass(clazz, true)) {
+ final Description toDescription = new StringDescription();
+ final Description mismatchDescription = new StringDescription();
+
+ checker.describeTo(toDescription);
+ checker.describeMismatch(mismatchDescription);
+ final String reason =
+ "\n" +
+ "Expected: is \"" + toDescription.toString() + "\"\n" +
+ " but : was \"" + mismatchDescription.toString() + "\"";
+
+ throw new AssertionError(reason);
+ }
+ }
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/IntegrationTest.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/IntegrationTest.java
new file mode 100644
index 00000000..a033802f
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/IntegrationTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+/**
+ * Marker interface used to separate unit tests from integration tests. All
+ * integration tests should be marked with:
+ * {@literal @Category}(IntegrationTest.class)
+ * so that they can be run separately.
+ */
+public interface IntegrationTest {
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/NullScheduledExecutor.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/NullScheduledExecutor.java
new file mode 100644
index 00000000..6d959703
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/NullScheduledExecutor.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A scheduled executor service that does not do any of the work scheduled to it.
+ * <p>
+ * This is useful for testing when you want to disable a background scheduled
+ * task.
+ * </p>
+ */
+public class NullScheduledExecutor implements ScheduledExecutorService {
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay,
+ TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,
+ TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
+ long initialDelay,
+ long period, TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
+ long initialDelay,
+ long delay,
+ TimeUnit unit) {
+ return null;
+ }
+
+ @Override
+ public void shutdown() {
+
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(
+ Collection<? extends Callable<T>> tasks, long timeout,
+ TimeUnit unit) throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
+ long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+
+ }
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestTools.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestTools.java
new file mode 100644
index 00000000..e2fcefce
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestTools.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Random;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.junit.Assert.fail;
+
+/**
+ * Utilities to aid in producing JUnit tests.
+ */
+public final class TestTools {
+
+ private static final Random RANDOM = new Random();
+
+ // Prohibit construction
+ private TestTools() {
+ }
+
+ public static void print(String msg) {
+ System.out.print(msg);
+ }
+
+ /**
+ * Suspends the current thread for a specified number of millis.
+ *
+ * @param ms number of millis
+ */
+ public static void delay(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ fail("test interrupted");
+ }
+ }
+
+ /**
+ * Returns the current time in millis since epoch.
+ *
+ * @return current time
+ */
+ public static long now() {
+ return System.currentTimeMillis();
+ }
+
+ /**
+ * Runs the specified runnable until it completes successfully or until the
+ * specified time expires. If the latter occurs, the first encountered
+ * assertion on the last attempt will be re-thrown. Errors other than
+ * assertion errors will be propagated immediately.
+ * <p>
+ * Assertions attempts will not be closer than 10 millis apart and no
+ * further than 50 millis.
+ * </p>
+ *
+ * @param delay number of millis to delay before the first attempt
+ * @param duration number of milliseconds beyond the current time
+ * @param assertions test assertions runnable
+ */
+ public static void assertAfter(int delay, int duration, Runnable assertions) {
+ checkArgument(delay < duration, "delay >= duration");
+ long start = now();
+ int step = Math.max(Math.min((duration - delay) / 100, 50), 10);
+
+ // Is there an initial delay?
+ if (delay > 0) {
+ delay(delay);
+ }
+
+ // Keep going until the assertions succeed or until time runs-out.
+ while (true) {
+ try {
+ assertions.run();
+ break;
+ } catch (AssertionError e) {
+ // If there was an error and time ran out, re-throw it.
+ if (now() - start > duration) {
+ throw e;
+ }
+ }
+ delay(step);
+ }
+ }
+
+ /**
+ * Runs the specified runnable until it completes successfully or until the
+ * specified time expires. If the latter occurs, the first encountered
+ * assertion on the last attempt will be re-thrown. Errors other than
+ * assertion errors will be propagated immediately.
+ * <p>
+ * Assertions attempts will not be closer than 10 millis apart and no
+ * further than 50 millis.
+ * </p>
+ *
+ * @param duration number of milliseconds beyond the current time
+ * @param assertions test assertions runnable
+ */
+ public static void assertAfter(int duration, Runnable assertions) {
+ assertAfter(0, duration, assertions);
+ }
+
+
+ /**
+ * Creates a directory tree of test files. To signify creating a directory
+ * file path should end with '/'.
+ *
+ * @param paths list of file paths
+ * @return list of created files
+ * @throws java.io.IOException if there is an issue
+ */
+ public static List<File> createTestFiles(List<String> paths) throws IOException {
+ return createTestFiles(paths, 32, 1024);
+ }
+
+ /**
+ * Creates a directory tree of test files. To signify creating a directory
+ * file path should end with '/'.
+ *
+ * @param paths list of file paths
+ * @param minSize minimum file size in bytes
+ * @param maxSize maximum file size in bytes
+ * @return list of created files
+ * @throws java.io.IOException if there is an issue
+ */
+ public static List<File> createTestFiles(List<String> paths,
+ int minSize, int maxSize) throws IOException {
+ ImmutableList.Builder<File> files = ImmutableList.builder();
+ for (String p : paths) {
+ File f = new File(p);
+ if (p.endsWith("/")) {
+ if (f.mkdirs()) {
+ files.add(f);
+ }
+ } else {
+ Files.createParentDirs(f);
+ if (f.createNewFile()) {
+ writeRandomFile(f, minSize, maxSize);
+ files.add(f);
+ }
+ }
+ }
+ return files.build();
+ }
+
+ /**
+ * Writes random binary content into the specified file. The number of
+ * bytes will be random between the given minimum and maximum.
+ *
+ * @param file file to write data to
+ * @param minSize minimum number of bytes to write
+ * @param maxSize maximum number of bytes to write
+ * @throws IOException if there is an issue
+ */
+ public static void writeRandomFile(File file, int minSize, int maxSize) throws IOException {
+ int size = minSize + (minSize == maxSize ? 0 : RANDOM.nextInt(maxSize - minSize));
+ byte[] data = new byte[size];
+ tweakBytes(RANDOM, data, size / 4);
+ Files.write(data, file);
+ }
+
+
+ /**
+ * Tweaks the given number of bytes in a byte array.
+ *
+ * @param random random number generator
+ * @param data byte array to be tweaked
+ * @param count number of bytes to tweak
+ */
+ public static void tweakBytes(Random random, byte[] data, int count) {
+ tweakBytes(random, data, count, 0, data.length);
+ }
+
+ /**
+ * Tweaks the given number of bytes in the specified range of a byte array.
+ *
+ * @param random random number generator
+ * @param data byte array to be tweaked
+ * @param count number of bytes to tweak
+ * @param start index at beginning of range (inclusive)
+ * @param end index at end of range (exclusive)
+ */
+ public static void tweakBytes(Random random, byte[] data, int count,
+ int start, int end) {
+ int len = end - start;
+ for (int i = 0; i < count; i++) {
+ data[start + random.nextInt(len)] = (byte) random.nextInt();
+ }
+ }
+
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestUtils.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestUtils.java
new file mode 100644
index 00000000..1afc4948
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/TestUtils.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+
+/**
+ * Utilities for testing.
+ */
+public final class TestUtils {
+
+ /**
+ * Sets the field, bypassing scope restriction.
+ *
+ * @param subject Object where the field belongs
+ * @param fieldName name of the field to set
+ * @param value value to set to the field.
+ * @param <T> subject type
+ * @param <U> value type
+ * @throws TestUtilsException if there are reflection errors while setting
+ * the field
+ */
+ public static <T, U> void setField(T subject, String fieldName, U value)
+ throws TestUtilsException {
+ @SuppressWarnings("unchecked")
+ Class clazz = subject.getClass();
+ try {
+ while (clazz != null) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ field.set(subject, value);
+ break;
+ } catch (NoSuchFieldException ex) {
+ if (clazz == clazz.getSuperclass()) {
+ break;
+ }
+ clazz = clazz.getSuperclass();
+ }
+ }
+ } catch (SecurityException | IllegalArgumentException |
+ IllegalAccessException e) {
+ throw new TestUtilsException("setField failed", e);
+ }
+ }
+
+ /**
+ * Gets the field, bypassing scope restriction.
+ *
+ * @param subject Object where the field belongs
+ * @param fieldName name of the field to get
+ * @return value of the field.
+ * @param <T> subject type
+ * @param <U> field value type
+ * @throws TestUtilsException if there are reflection errors while getting
+ * the field
+ */
+ public static <T, U> U getField(T subject, String fieldName)
+ throws TestUtilsException {
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) subject.getClass();
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+
+ @SuppressWarnings("unchecked")
+ U result = (U) field.get(subject);
+ return result;
+ } catch (NoSuchFieldException | SecurityException |
+ IllegalArgumentException | IllegalAccessException e) {
+ throw new TestUtilsException("getField failed", e);
+ }
+ }
+
+ /**
+ * Calls the method, bypassing scope restriction.
+ *
+ * @param subject Object where the method belongs
+ * @param methodName name of the method to call
+ * @param paramTypes formal parameter type array
+ * @param args arguments
+ * @return return value or null if void
+ * @param <T> subject type
+ * @param <U> return value type
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the method
+ */
+ public static <T, U> U callMethod(T subject, String methodName,
+ Class<?>[] paramTypes, Object...args) throws TestUtilsException {
+
+ try {
+ @SuppressWarnings("unchecked")
+ Class<T> clazz = (Class<T>) subject.getClass();
+ final Method method;
+ if (paramTypes == null || paramTypes.length == 0) {
+ method = clazz.getDeclaredMethod(methodName);
+ } else {
+ method = clazz.getDeclaredMethod(methodName, paramTypes);
+ }
+ method.setAccessible(true);
+
+ @SuppressWarnings("unchecked")
+ U result = (U) method.invoke(subject, args);
+ return result;
+ } catch (NoSuchMethodException | SecurityException |
+ IllegalAccessException | IllegalArgumentException |
+ InvocationTargetException e) {
+ throw new TestUtilsException("callMethod failed", e);
+ }
+ }
+
+ /**
+ * Calls the method, bypassing scope restriction.
+ *
+ * @param subject Object where the method belongs
+ * @param methodName name of the method to call
+ * @param paramType formal parameter type
+ * @param arg argument
+ * @return return value or null if void
+ * @param <T> subject type
+ * @param <U> return value type
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the method
+ */
+ public static <T, U> U callMethod(T subject, String methodName,
+ Class<?> paramType, Object arg) throws TestUtilsException {
+ return callMethod(subject, methodName, new Class<?>[]{paramType}, arg);
+ }
+
+ /**
+ * Triggers an allocation of an object of type T and forces a call to
+ * the private constructor.
+ *
+ * @param constructor Constructor to call
+ * @param <T> type of the object to create
+ * @return created object of type T
+ * @throws TestUtilsException if there are reflection errors while calling
+ * the constructor
+ */
+ public static <T> T callConstructor(Constructor<T> constructor)
+ throws TestUtilsException {
+ try {
+ constructor.setAccessible(true);
+ return constructor.newInstance();
+ } catch (InstantiationException | IllegalAccessException |
+ InvocationTargetException error) {
+ throw new TestUtilsException("callConstructor failed", error);
+ }
+ }
+
+ /**
+ * Avoid instantiation.
+ */
+ private TestUtils() {}
+
+ /**
+ * Exception that can be thrown if problems are encountered while executing
+ * the utility method. These are usually problems accessing fields/methods
+ * through reflection. The original exception can be found by examining the
+ * cause.
+ */
+ public static class TestUtilsException extends Exception {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs a new exception with the specified detail message and
+ * cause.
+ *
+ * @param message the detail message
+ * @param cause the original cause of this exception
+ */
+ public TestUtilsException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/UtilityClassChecker.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/UtilityClassChecker.java
new file mode 100644
index 00000000..9c623cee
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/UtilityClassChecker.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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.
+ */
+package org.onlab.junit;
+
+import org.hamcrest.Description;
+import org.hamcrest.StringDescription;
+import org.onlab.junit.TestUtils.TestUtilsException;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * Hamcrest style class for verifying that a class follows the
+ * accepted rules for utility classes.
+ *
+ * The rules that are enforced for utility classes:
+ * - the class must be declared final
+ * - the class must have only one constructor
+ * - the constructor must be private and inaccessible to callers
+ * - the class must have only static methods
+ */
+
+public class UtilityClassChecker {
+
+ private String failureReason = "";
+
+ /**
+ * Method to determine if a given class is a properly specified
+ * utility class. In addition to checking that the class meets the criteria
+ * for utility classes, an object of the class type is allocated to force
+ * test code coverage onto the class constructor.
+ *
+ * @param clazz the class to check
+ * @return true if the given class is a properly specified utility class.
+ */
+ private boolean isProperlyDefinedUtilityClass(Class<?> clazz) {
+ // class must be declared final
+ if (!Modifier.isFinal(clazz.getModifiers())) {
+ failureReason = "a class that is not final";
+ return false;
+ }
+
+ // class must have only one constructor
+ final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
+ if (constructors.length != 1) {
+ failureReason = "a class with more than one constructor";
+ return false;
+ }
+
+ // constructor must not be accessible outside of the class
+ final Constructor<?> constructor = constructors[0];
+ if (constructor.isAccessible()) {
+ failureReason = "a class with an accessible default constructor";
+ return false;
+ }
+
+ // constructor must be private
+ if (!Modifier.isPrivate(constructor.getModifiers())) {
+ failureReason = "a class with a default constructor that is not private";
+ return false;
+ }
+
+ // class must have only static methods
+ for (final Method method : clazz.getMethods()) {
+ if (method.getDeclaringClass().equals(clazz)) {
+ if (!Modifier.isStatic(method.getModifiers())) {
+ failureReason = "a class with one or more non-static methods";
+ return false;
+ }
+ }
+
+ }
+
+ try {
+ final Object newObject = TestUtils.callConstructor(constructor);
+ if (newObject == null) {
+ failureReason = "could not instantiate a new object";
+ return false;
+ }
+ } catch (TestUtilsException e) {
+ failureReason = "could not instantiate a new object";
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Describe why an error was reported. Uses Hamcrest style Description
+ * interfaces.
+ *
+ * @param description the Description object to use for reporting the
+ * mismatch
+ */
+ public void describeMismatch(Description description) {
+ description.appendText(failureReason);
+ }
+
+ /**
+ * Describe the source object that caused an error, using a Hamcrest
+ * Matcher style interface. In this case, it always returns
+ * that we are looking for a properly defined utility class.
+ *
+ * @param description the Description object to use to report the "to"
+ * object
+ */
+ public void describeTo(Description description) {
+ description.appendText("a properly defined utility class");
+ }
+
+ /**
+ * Assert that the given class adheres to the utility class rules.
+ *
+ * @param clazz the class to check
+ *
+ * @throws java.lang.AssertionError if the class is not a valid
+ * utility class
+ */
+ public static void assertThatClassIsUtility(Class<?> clazz) {
+ final UtilityClassChecker checker = new UtilityClassChecker();
+ if (!checker.isProperlyDefinedUtilityClass(clazz)) {
+ final Description toDescription = new StringDescription();
+ final Description mismatchDescription = new StringDescription();
+
+ checker.describeTo(toDescription);
+ checker.describeMismatch(mismatchDescription);
+ final String reason =
+ "\n" +
+ "Expected: is \"" + toDescription.toString() + "\"\n" +
+ " but : was \"" + mismatchDescription.toString() + "\"";
+
+ throw new AssertionError(reason);
+ }
+ }
+}
diff --git a/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/package-info.java b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/package-info.java
new file mode 100644
index 00000000..379e4a04
--- /dev/null
+++ b/framework/src/onos/utils/junit/src/main/java/org/onlab/junit/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * Utilities to assist in developing JUnit tests.
+ */
+package org.onlab.junit;