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

net.freeutils.util.Tests Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2003-2024 Amichai Rothman
 *
 *  This file is part of JElementary - the Java Elementary Utilities package.
 *
 *  JElementary is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JElementary is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JElementary.  If not, see .
 *
 *  For additional info see https://www.freeutils.net/source/jelementary/
 */

package net.freeutils.util;

import java.security.Permission;
import java.util.Arrays;

/**
 * The {@code Tests} class facilitates easier and more concise unit tests.
 */
public class Tests {

    /**
     * The {@code Testable} interface is a callback used to run a single test's processing.
     *
     * @param 

the parameter type * @param the result type */ public interface Testable { /** * Runs a test using the given parameter as input, and returning the test output. * * @param param the test parameter; can be an array if multiple parameters are needed * @return the test result * @throws Throwable when running the test with the given input throws any Throwable */ R run(P param) throws Throwable; } /** * Asserts that an actual result is equal to the expected result. * Arrays are deeply-compared. * * @param message the exception message to use if the assertion fails * @param expected the expected result * @param actual the actual result * @throws AssertionError if the actual result is not equal to the expected result */ private static void assertEquals(String message, Object expected, Object actual) { // we'd use junit assertions here, but we don't want to have any dependencies if (expected == actual || expected != null && expected.equals(actual)) return; Object[] expectedArray = { expected }; Object[] resultArray = { actual }; if (Arrays.deepEquals(expectedArray, resultArray)) return; throw new AssertionError(message + ": expected " + Arrays.deepToString(expectedArray) + ", got " + Arrays.deepToString(resultArray)); } /** * Runs one or more tests. *

* Each test data consists of an array of objects where the * first element is a test description message (type String), the second is the * parameter(s) to pass to the testable (type P) and the third is the expected * result (type R). For tests which require multiple parameters, P can be an * array holding all required parameters. *

* Alternatively, the test data may consist of an array of only two objects, * the parameter(s) and the result, and the message is taken to be the string * representation of the parameter. *

* For each test, the test data is passed to the testable, and the result is * compared to the expected result for equality. If the expected result is * a Class object representing a Throwable, then it is expected that running * the test will throw an exception of that type. *

* In both cases, if the expected behavior occurs (same result or exception), * the test passes. If not, an appropriate assertion error is thrown with the * corresponding message. * * @param

the parameter type * @param the result type * @param testable the test operation * @param tests the test data to operate on * @throws AssertionError if any of the tests fail */ @SuppressWarnings("unchecked") public static void test(Tests.Testable testable, Object[]... tests) { for (Object[] test : tests) { P param = (P)test[test.length - 2]; R expected = (R)test[test.length - 1]; String message = test.length > 2 ? (String)test[0] : "param '" + param + "'"; boolean fail = expected instanceof Class && Throwable.class.isAssignableFrom((Class)expected); try { if (message == null) message = String.valueOf(param); R result = testable.run(param); if (fail) throw new AssertionError(message + " should throw " + ((Class)expected).getName()); assertEquals(message, expected, result); } catch (Throwable t) { if (t instanceof AssertionError) throw (AssertionError)t; if (!fail || !((Class)expected).isInstance(t)) throw new AssertionError(message + ": unexpected exception " + t, t); } } } /** * Runs one or more tests. *

* Each test data consists of an array of objects where the * first element is a test description message (type String) and the second is * the parameter(s) to pass to the testable (type P). For tests which require * multiple parameters, P can be an array holding all required parameters. *

* Alternatively, the test data may consist of an array of only a single object, * the parameter(s), and the message is taken to be the string representation * of the parameter. *

* For each test, the test data is passed to the both testables. If the * testable's behavior matches the expected testable's behavior (same result or * exception), the test passes. If not, an appropriate assertion error is thrown * with the corresponding message. * * @param

the parameter type * @param the result type * @param testable the test operation * @param expectedTestable the test operation providing the expected behavior * @param tests the test data to operate on */ @SuppressWarnings("unchecked") public static void test(Tests.Testable testable, Tests.Testable expectedTestable, Object[]... tests) { for (Object[] test : tests) { if (test.length > 2) throw new IllegalArgumentException("test using expectedTestable cannot specify explicit result"); P param = (P)test[test.length - 1]; try { R expected = expectedTestable.run(param); test = Containers.concat(test, expected); } catch (Throwable t) { test = Containers.concat(test, t.getClass()); } test(testable, test); } } /** * The {@code SystemExitException} is thrown when System.exit() is called * after {@link #preventSystemExit} has been called. */ public static class SystemExitException extends SecurityException { private static final long serialVersionUID = -1273329516299366346L; private final int status; public SystemExitException(int status) { super("System.exit() called with status " + status); this.status = status; } /** * Returns the status (exit code) that was passed to System.exit(). * * @return the status (exit code) that was passed to System.exit() */ public int getStatus() { return status; } } /** * The {@code NoSystemExitSecurityManager} prevents calls to System.exit(), * throwing a {@link SystemExitException} instead */ public static class NoSystemExitSecurityManager extends SecurityManager { private final SecurityManager prev; public NoSystemExitSecurityManager(final SecurityManager manager) { prev = manager; } @Override public void checkExit(int status) { if (prev != null) prev.checkExit(status); throw new SystemExitException(status); } @Override public void checkPermission(Permission perm) { if (prev != null) prev.checkPermission(perm); } } /** * Installs or removes a SecurityManager that prevents * {@link System#exit} calls from working by throwing an * {@link SystemExitException} instead. * * @param noExit if true, exit calls are prevented; * if false, exit calls are allowed */ public static void preventSystemExit(boolean noExit) { SecurityManager manager = System.getSecurityManager(); if (manager instanceof NoSystemExitSecurityManager) { if (!noExit) System.setSecurityManager(((NoSystemExitSecurityManager)manager).prev); } else { if (noExit) System.setSecurityManager(new NoSystemExitSecurityManager(manager)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy