diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/utils/junit/src/main/java | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/utils/junit/src/main/java')
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; |