Java tutorial
/* Copyright 2009-2015 David Hadka * * This file is part of the MOEA Framework. * * The MOEA Framework is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The MOEA Framework 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 Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the MOEA Framework. If not, see <http://www.gnu.org/licenses/>. */ package org.moeaframework; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.io.Writer; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.BitSet; import java.util.List; import java.util.regex.Pattern; import org.apache.commons.lang3.SystemUtils; import org.apache.commons.math3.linear.RealMatrix; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; import org.junit.Assert; import org.junit.Assume; import org.moeaframework.core.Population; import org.moeaframework.core.Problem; import org.moeaframework.core.Settings; import org.moeaframework.core.Solution; import org.moeaframework.core.variable.EncodingUtils; import org.moeaframework.util.io.CommentedLineReader; /** * Utility methods for testing. */ public class TestUtils { /** * Default floating-point equality allowing 5% relative error. */ public static final FloatingPointError DEFAULT_ERROR = new RelativeError(0.05); /** * Private constructor to prevent instantiation. */ private TestUtils() { super(); } /** * Asserts that two double matrices are equal. * * @param expected the expected matrix * @param actual the actual matrix */ public static void assertEquals(double[][] expected, double[][] actual) { Assert.assertEquals(expected.length, actual.length); for (int i = 0; i < expected.length; i++) { Assert.assertEquals(expected[i].length, actual[i].length); for (int j = 0; j < expected[i].length; j++) { assertEquals(expected[i][j], actual[i][j]); } } } /** * Asserts that two populations are equal using the * {@link #equals(Population, Population)} method. * * @param p1 the first population * @param p2 the second population */ public static void assertEquals(Population p1, Population p2) { Assert.assertTrue(equals(p1, p2)); } /** * Returns {@code true} if the two populations are equal; otherwise * {@code false}. Two populations are equal if all solutions contained * in one population are contained in the other population. * * @param p1 the first population * @param p2 the second population * @return {@code true} if the two populations are equal; otherwise * {@code false} */ public static boolean equals(Population p1, Population p2) { if (p1.size() != p2.size()) { return false; } BitSet matched1 = new BitSet(p1.size()); BitSet matched2 = new BitSet(p2.size()); for (int i = 0; i < p1.size(); i++) { for (int j = 0; j < p2.size(); j++) { if (equals(p1.get(i), p2.get(j))) { matched1.set(i); matched2.set(j); } } } return (matched1.cardinality() == p1.size()) && (matched2.cardinality() == p2.size()); } /** * Asserts that the two solutions are equal using the * {@link #equals(Solution, Solution)} method. * * @param s1 the first solution * @param s2 the second solution */ public static void assertEquals(Solution s1, Solution s2) { Assert.assertTrue(equals(s1, s2)); } /** * Returns {@code true} if the two solutions are equal; {@code false} * otherwise. This method is not supported in the core library as the * process of comparing solutions is often domain-specific. * * @param s1 the first solution * @param s2 the second solution * @return {@code true} if the two solutions are equal; {@code false} * otherwise */ public static boolean equals(Solution s1, Solution s2) { if (s1.getNumberOfVariables() != s2.getNumberOfVariables()) { return false; } if (s1.getNumberOfObjectives() != s2.getNumberOfObjectives()) { return false; } if (s1.getNumberOfConstraints() != s2.getNumberOfConstraints()) { return false; } for (int i = 0; i < s1.getNumberOfVariables(); i++) { if (!s1.getVariable(i).equals(s2.getVariable(i))) { return false; } } for (int i = 0; i < s1.getNumberOfObjectives(); i++) { if (Math.abs(s1.getObjective(i) - s2.getObjective(i)) >= TestThresholds.SOLUTION_EPS) { return false; } } for (int i = 0; i < s1.getNumberOfConstraints(); i++) { if (Math.abs(s1.getConstraint(i) - s2.getConstraint(i)) >= TestThresholds.SOLUTION_EPS) { return false; } } //TODO: the attributes check does not work correctly if it contains //arrays since it's using an identity check and not a value check //return s1.getAttributes().equals(s2.getAttributes()); return true; } /** * Creates a temporary file for testing purposes. The temporary file will * be deleted on exit. * * @return the temporary file * @throws IOException if an I/O error occurred */ public static File createTempFile() throws IOException { File file = File.createTempFile("test", null); file.deleteOnExit(); return file; } /** * Creates a temporary file containing the specified data. The temporary * file will be deleted on exit. * * @param data the contents of the temporary file * @return the temporary file * @throws IOException if an I/O error occurred */ public static File createTempFile(String data) throws IOException { File file = createTempFile(); Writer writer = null; try { writer = new BufferedWriter(new FileWriter(file)); writer.write(data); } finally { if (writer != null) { writer.close(); } } return file; } /** * Returns a new solution with the specified objective values. * * @param objectives the objective values * @return a new solution with the specified objective values */ public static Solution newSolution(double... objectives) { return new Solution(objectives); } /** * Returns the solution resulting from evaluating the problem with the * specified decision variables. * * @param problem the problem * @param variables the decision variable values * @return the solution resulting from evaluating the problem with the * specified decision variables */ public static Solution evaluateAt(Problem problem, double... variables) { Solution solution = problem.newSolution(); EncodingUtils.setReal(solution, variables); problem.evaluate(solution); return solution; } /** * Returns the number of lines in the specified file. * * @param file the file * @return the number of lines in the specified file * @throws IOException if an I/O error occurred */ public static int lineCount(File file) throws IOException { BufferedReader reader = null; int count = 0; try { reader = new BufferedReader(new FileReader(file)); while (reader.readLine() != null) { count++; } } finally { if (reader != null) { reader.close(); } } return count; } /** * Asserts that every line on the file matches the specified regular * expression pattern. This method automatically ignores commented lines. * * @param file the file * @param regex the regular expression pattern * @throws IOException if an I/O error occurred */ public static void assertLinePattern(File file, String regex) throws IOException { CommentedLineReader reader = null; Pattern pattern = Pattern.compile(regex); try { reader = new CommentedLineReader(new FileReader(file)); String line = null; while ((line = reader.readLine()) != null) { if (!pattern.matcher(line).matches()) { Assert.fail("line does not match pattern: " + line); } } } finally { if (reader != null) { reader.close(); } } } /** * Invokes the main method of the specified command line utility, * redirecting the output to the specified file. As this method redirects * the {@code System.out} stream, this must be the only process writing to * {@code System.out}. * * @param output the file for output redirection * @param tool the command line utility class * @param args the command line arguments * @throws Exception if any of the many exceptions for reflection or * file writing occurred */ public static void pipeCommandLine(File output, Class<?> tool, String... args) throws Exception { PrintStream oldOut = System.out; PrintStream newOut = null; try { newOut = new PrintStream(new FileOutputStream(output)); System.setOut(newOut); Method mainMethod = tool.getMethod("main", String[].class); mainMethod.invoke(null, (Object) args); } finally { if (newOut != null) { newOut.close(); } System.setOut(oldOut); } } /** * Invokes the main method of the specified command line utility, * redirecting the output and error streams to the specified files. As * this method redirects the {@code System.out} and {@code System.err} * streams, this must be the only process writing to {@code System.out} * and {@code System.err}. * * @param output the file for output redirection * @param error the file for error redirection * @param tool the command line utility class * @param args the command line arguments * @throws Exception if any of the many exceptions for reflection or * file writing occurred */ public static void pipeCommandLine(File output, File error, Class<?> tool, String... args) throws Exception { PrintStream oldErr = System.err; PrintStream newErr = null; try { newErr = new PrintStream(new FileOutputStream(error)); System.setErr(newErr); pipeCommandLine(output, tool, args); } finally { if (newErr != null) { newErr.close(); } System.setErr(oldErr); } } /** * Asserts that the two floating-point values are equal. * * @param d1 the first floating-point value * @param d2 the second floating-point value */ public static void assertEquals(double d1, double d2) { try { DEFAULT_ERROR.assertEquals(d1, d2); } catch (AssertionError e) { //relative equality breaks down when the values approach 0; this //is an attempt to correct using absolute difference if (((d1 != 0.0) && (d2 != 0.0)) || (Math.abs(d1 - d2) > 0.05)) { throw e; } } } /** * Asserts that the two matrices are equal. * * @param rm1 the first matrix * @param rm2 the second matrix * @param error the equality comparison used to assert pairwise values * are equal */ public static void assertEquals(RealMatrix rm1, RealMatrix rm2, FloatingPointError error) { Assert.assertEquals(rm1.getRowDimension(), rm2.getRowDimension()); Assert.assertEquals(rm1.getColumnDimension(), rm2.getColumnDimension()); for (int i = 0; i < rm1.getRowDimension(); i++) { for (int j = 0; j < rm2.getColumnDimension(); j++) { error.assertEquals(rm1.getEntry(i, j), rm2.getEntry(i, j)); } } } /** * Loads the contents of the specified file. * * @param file the file to load * @return the contents of the file * @throws IOException if an I/O error occurred */ public static byte[] loadFile(File file) throws IOException { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); FileInputStream fis = null; int length = 0; byte[] buffer = new byte[Settings.BUFFER_SIZE]; try { fis = new FileInputStream(file); while ((length = fis.read(buffer)) != -1) { bytes.write(buffer, 0, length); } return bytes.toByteArray(); } finally { if (fis != null) { fis.close(); } } } /** * Loads the contents of the specified file, returning the matrix of values * stored in the file. * * @param file the file to load * @return the matrix of values stored in the file * @throws IOException if an I/O error occurred */ public static double[][] loadMatrix(File file) throws IOException { List<double[]> data = new ArrayList<double[]>(); CommentedLineReader reader = null; String line = null; try { reader = new CommentedLineReader(new FileReader(file)); while ((line = reader.readLine()) != null) { String[] tokens = line.split("\\s+"); double[] row = new double[tokens.length]; for (int i = 0; i < tokens.length; i++) { row[i] = Double.parseDouble(tokens[i]); } data.add(row); } return data.toArray(new double[data.size()][]); } finally { if (reader != null) { reader.close(); } } } /** * Asserts that the statistical distribution satisfies the properties of an * integer-valued uniform distribution between {@code min} and {@code max}. * * @param min the minimum bounds of the uniform distribution * @param max the maximum bounds of the uniform distribution * @param statistics the captures statistics of a sampled distribution */ public static void assertUniformDistribution(int min, int max, DescriptiveStatistics statistics) { int n = max - min + 1; int nn = n * n; assertEquals((min + max) / 2.0, statistics.getMean()); assertEquals((nn - 1) / 12.0, statistics.getVariance()); assertEquals(0.0, statistics.getSkewness()); assertEquals(-(6.0 * (nn + 1)) / (5.0 * (nn - 1)), statistics.getKurtosis()); assertEquals(min, statistics.getMin()); assertEquals(max, statistics.getMax()); } /** * Returns the regular expression pattern for detecting any number of * numeric values separated by whitespace. * * @param n the number of numeric values to detect * @return the regular expression pattern for detecting any number of * numeric values separated by whitespace */ public static String getSpaceSeparatedNumericPattern(int n) { return "(-?[0-9]+(\\.[0-9]+(E-?[0-9]+)?)?\\b\\s*){" + n + "}"; } /** * Returns the regular expression pattern for detecting any number of * numeric values separated by a comma. * * @param n the number of numeric values to detect * @return the regular expression pattern for detecting any number of * numeric values separated by a comma */ public static String getCommaSeparatedNumericPattern(int n) { String pattern = ""; if (n > 0) { pattern = "(-?[0-9]+(\\.[0-9]+(E-?[0-9]+)?)?)"; } if (n > 1) { pattern += "(\\s*,\\s*-?[0-9]+(\\.[0-9]+(E-?[0-9]+)?)?){" + (n - 1) + "}"; } return pattern; } /** * Extracts the data stored in a resource, saving its contents to a * temporary file. If the resource name contains an extension, the file * will be created with the extension. * * @param resource the name of the resource to extract * @return the temporary file containing the resource data * @throws IOException if an I/O error occurred */ public static File extractResource(String resource) throws IOException { InputStream input = null; OutputStream output = null; byte[] buffer = new byte[Settings.BUFFER_SIZE]; int len = -1; //determine the file extension, if any File file = null; int position = resource.indexOf('.', resource.lastIndexOf('/')); if (position < 0) { file = TestUtils.createTempFile(); } else { file = File.createTempFile("test", resource.substring(position)); file.deleteOnExit(); } //copy the resource contents to the file try { input = TestUtils.class.getResourceAsStream(resource); if (input == null) { throw new IOException("resource not found: " + resource); } try { output = new FileOutputStream(file); while ((len = input.read(buffer)) != -1) { output.write(buffer, 0, len); } } finally { if (output != null) { output.close(); } } } finally { if (input != null) { input.close(); } } return file; } /** * Skips the current test if the file does not exist. * * @param file the file to check */ public static void assumeFileExists(File file) { if (!file.exists()) { System.err.println(file + " does not exist, skipping test"); Assume.assumeTrue(false); } } /** * Skips the current test if the machine is not POSIX-compliant. */ public static void assumePOSIX() { if (!SystemUtils.IS_OS_UNIX) { System.err.println("system is not POSIX-compliant, skipping test"); Assume.assumeTrue(false); } } /** * Attempts to run make in the given folder. If make is not successful, * the test is skipped. * * @param folder the folder in which make is executed */ public static void runMake(File folder) { System.out.println("Running make to build test executables"); try { Process process = Runtime.getRuntime().exec("make", null, folder); if (process.waitFor() != 0) { System.err.println("make exited with an error status (" + process.exitValue() + "), skipping test"); Assume.assumeTrue(false); } } catch (InterruptedException e) { System.err.println("interrupted while waiting for make to " + "complete, skipping test"); Assume.assumeTrue(false); } catch (IOException e) { System.err.println("unable to run make, skipping test"); Assume.assumeTrue(false); } } }