diff options
author | 2015-09-09 22:15:21 -0700 | |
---|---|---|
committer | 2015-09-09 22:15:21 -0700 | |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/utils/junit | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/utils/junit')
13 files changed, 1540 insertions, 0 deletions
diff --git a/framework/src/onos/utils/junit/pom.xml b/framework/src/onos/utils/junit/pom.xml new file mode 100644 index 00000000..90178346 --- /dev/null +++ b/framework/src/onos/utils/junit/pom.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onlab-utils</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onlab-junit</artifactId> + <packaging>bundle</packaging> + + <description>ON.Lab JUnit test utilities</description> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava-testlib</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-core</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <scope>compile</scope> + </dependency> + </dependencies> + +</project> 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; diff --git a/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/ImmutableClassCheckerTest.java b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/ImmutableClassCheckerTest.java new file mode 100644 index 00000000..cd5a8c1a --- /dev/null +++ b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/ImmutableClassCheckerTest.java @@ -0,0 +1,135 @@ +/* + * 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.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.onlab.junit.ImmutableClassChecker.assertThatClassIsImmutable; + +/** + * Set of unit tests to check the implementation of the immutable class + * checker. + */ +public class ImmutableClassCheckerTest { + /** + * Test class for non final class check. + */ + // CHECKSTYLE IGNORE FinalClass FOR NEXT 1 LINES + static class NonFinal { + private NonFinal() { } + } + + /** + * Check that a non final class correctly produces an error. + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testNonFinalClass() throws Exception { + boolean gotException = false; + try { + assertThatClassIsImmutable(NonFinal.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("is not final")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class for non private member class check. + */ + static final class FinalProtectedMember { + protected final int x = 0; + } + + /** + * Check that a final class with a non-private member is properly detected. + * + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testFinalProtectedMember() throws Exception { + boolean gotException = false; + try { + assertThatClassIsImmutable(FinalProtectedMember.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("a field named 'x' that is not private")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class for non private member class check. + */ + static final class NotFinalPrivateMember { + private int x = 0; + } + + /** + * Check that a final class with a non-final private + * member is properly detected. + * + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testNotFinalPrivateMember() throws Exception { + boolean gotException = false; + try { + assertThatClassIsImmutable(NotFinalPrivateMember.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("a field named 'x' that is not final")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class for non private member class check. + */ + static final class ClassWithSetter { + private final int x = 0; + public void setX(int newX) { + } + } + + /** + * Check that a final class with a final private + * member that is modifyable by a setter is properly detected. + * + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testClassWithSetter() throws Exception { + boolean gotException = false; + try { + assertThatClassIsImmutable(ClassWithSetter.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("a class with a setter named 'setX'")); + gotException = true; + } + assertThat(gotException, is(true)); + } + +} + diff --git a/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestToolsTest.java b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestToolsTest.java new file mode 100644 index 00000000..d11cea0b --- /dev/null +++ b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestToolsTest.java @@ -0,0 +1,47 @@ +/* + * 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.junit.Test; + +import static org.junit.Assert.*; +import static org.onlab.junit.TestTools.assertAfter; + +public class TestToolsTest { + + @Test + public void testSuccess() { + assertAfter(10, 100, new Runnable() { + int count = 0; + @Override + public void run() { + if (count++ < 3) { + assertTrue(false); + } + } + }); + } + + @Test(expected = AssertionError.class) + public void testFailure() { + assertAfter(100, new Runnable() { + @Override + public void run() { + assertTrue(false); + } + }); + } +} diff --git a/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestUtilsTest.java b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestUtilsTest.java new file mode 100644 index 00000000..68e407fc --- /dev/null +++ b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/TestUtilsTest.java @@ -0,0 +1,185 @@ +/* + * 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 static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.junit.TestUtils.TestUtilsException; + +/** + * Test and usage examples for TestUtils. + */ +public class TestUtilsTest { + + /** + * Test data. + */ + private static final class TestClass { + + @SuppressWarnings("unused") + private int privateField = 42; + + @SuppressWarnings("unused") + protected int protectedField = 2501; // CHECKSTYLE IGNORE THIS LINE + + /** + * Protected method with multiple argument. + * + * @param x simply returns + * @param y not used + * @return x + */ + @SuppressWarnings("unused") + private int privateMethod(Number x, Long y) { + return x.intValue(); + } + + /** + * Protected method with no argument. + * + * @return int + */ + @SuppressWarnings("unused") + protected int protectedMethod() { + return 42; + } + + /** + * Method returning array. + * + * @param ary random array + * @return ary + */ + @SuppressWarnings("unused") + private int[] arrayReturnMethod(int[] ary) { + return ary; + } + + /** + * Method without return value. + * + * @param s ignored + */ + @SuppressWarnings("unused") + private void voidMethod(String s) { + System.out.println(s); + } + } + + private TestClass test; + + /** + * Sets up the test fixture. + */ + @Before + public void setUp() { + test = new TestClass(); + } + + /** + * Example to access private field. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testSetGetPrivateField() throws TestUtilsException { + + assertEquals(42, (int) TestUtils.getField(test, "privateField")); + TestUtils.setField(test, "privateField", 0xDEAD); + assertEquals(0xDEAD, (int) TestUtils.getField(test, "privateField")); + } + + /** + * Example to access protected field. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testSetGetProtectedField() throws TestUtilsException { + + assertEquals(2501, (int) TestUtils.getField(test, "protectedField")); + TestUtils.setField(test, "protectedField", 0xBEEF); + assertEquals(0xBEEF, (int) TestUtils.getField(test, "protectedField")); + } + + /** + * Example to call private method and multiple parameters. + * <p/> + * It also illustrates that paramTypes must match declared type, + * not the runtime types of arguments. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testCallPrivateMethod() throws TestUtilsException { + + int result = TestUtils.callMethod(test, "privateMethod", + new Class<?>[] {Number.class, Long.class}, + Long.valueOf(42), Long.valueOf(32)); + assertEquals(42, result); + } + + /** + * Example to call protected method and no parameters. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testCallProtectedMethod() throws TestUtilsException { + + int result = TestUtils.callMethod(test, "protectedMethod", + new Class<?>[] {}); + assertEquals(42, result); + } + + /** + * Example to call method returning array. + * <p/> + * Note: It is not required to receive as Object. + * Following is just verifying it is not Boxed arrays. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testCallArrayReturnMethod() throws TestUtilsException { + + int[] array = {1, 2, 3}; + Object aryResult = TestUtils.callMethod(test, "arrayReturnMethod", + new Class<?>[] {int[].class}, array); + assertEquals(int[].class, aryResult.getClass()); + assertArrayEquals(array, (int[]) aryResult); + } + + + /** + * Example to call void returning method. + * <p/> + * Note: Return value will be null for void methods. + * + * @throws TestUtilsException TestUtils error + */ + @Test + public void testCallVoidReturnMethod() throws TestUtilsException { + + Object voidResult = TestUtils.callMethod(test, "voidMethod", + String.class, "foobar"); + assertNull(voidResult); + } +} diff --git a/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/UtilityClassCheckerTest.java b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/UtilityClassCheckerTest.java new file mode 100644 index 00000000..8c2c5532 --- /dev/null +++ b/framework/src/onos/utils/junit/src/test/java/org/onlab/junit/UtilityClassCheckerTest.java @@ -0,0 +1,160 @@ +/* + * 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.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.onlab.junit.UtilityClassChecker.assertThatClassIsUtility; + +/** + * Set of unit tests to check the implementation of the utility class + * checker. + */ +public class UtilityClassCheckerTest { + + // CHECKSTYLE:OFF test data intentionally not final + /** + * Test class for non final class check. + */ + static class NonFinal { + private NonFinal() { } + } + // CHECKSTYLE:ON + + /** + * Check that a non final class correctly produces an error. + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testNonFinalClass() throws Exception { + boolean gotException = false; + try { + assertThatClassIsUtility(NonFinal.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("is not final")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class for final no constructor class check. + */ + static final class FinalNoConstructor { + } + + /** + * Check that a final class with no declared constructor correctly produces + * an error. In this case, the compiler generates a default constructor + * for you, but the constructor is 'protected' and will fail the check. + * + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testFinalNoConstructorClass() throws Exception { + boolean gotException = false; + try { + assertThatClassIsUtility(FinalNoConstructor.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("class with a default constructor that " + + "is not private")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class for class with more than one constructor check. + */ + static final class TwoConstructors { + private TwoConstructors() { } + private TwoConstructors(int x) { } + } + + /** + * Check that a non static class correctly produces an error. + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testOnlyOneConstructor() throws Exception { + boolean gotException = false; + try { + assertThatClassIsUtility(TwoConstructors.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("more than one constructor")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class with a non private constructor. + */ + static final class NonPrivateConstructor { + protected NonPrivateConstructor() { } + } + + /** + * Check that a class with a non private constructor correctly + * produces an error. + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testNonPrivateConstructor() throws Exception { + + boolean gotException = false; + try { + assertThatClassIsUtility(NonPrivateConstructor.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("constructor that is not private")); + gotException = true; + } + assertThat(gotException, is(true)); + } + + /** + * Test class with a non static method. + */ + static final class NonStaticMethod { + private NonStaticMethod() { } + public void aPublicMethod() { } + } + + /** + * Check that a class with a non static method correctly produces an error. + * @throws Exception if any of the reflection lookups fail. + */ + @Test + public void testNonStaticMethod() throws Exception { + + boolean gotException = false; + try { + assertThatClassIsUtility(NonStaticMethod.class); + } catch (AssertionError assertion) { + assertThat(assertion.getMessage(), + containsString("one or more non-static methods")); + gotException = true; + } + assertThat(gotException, is(true)); + } +} |