/*==========================================================================*\ | $Id: TestCase.java,v 1.15 2012/03/05 14:17:44 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2007-2012 Virginia Tech | | This file is part of the Student-Library. | | The Student-Library 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 Student-Library 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 Student-Library; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package student; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; import junit.framework.AssertionFailedError; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Rule; import student.testingsupport.junit4.AdaptiveTimeout; import student.testingsupport.junit4.MixRunner; import student.testingsupport.MutableStringBufferInputStream; import student.testingsupport.PrintStreamWithHistory; import student.testingsupport.PrintWriterWithHistory; import student.testingsupport.StringNormalizer; import student.testingsupport.SystemIOUtilities; //------------------------------------------------------------------------- /** * This class provides some customized behavior beyond the basics of * {@link junit.framework.TestCase} to support testing of I/O driven * programs and flexible/fuzzy comparison of strings. In most cases, it * can be used as a completely transparent drop-in replacement for its * parent class. * <p> * Fuzzy string comparisons in this class default to standard rules in * {@link StringNormalizer} (excluding the OPT_* rules). You can use * the {@link #stringNormalizer()} method to access the normalizer and * set your own comparison options, however. * </p> * * @author Stephen Edwards * @author Last changed by $Author: stedwar2 $ * @version $Revision: 1.15 $, $Date: 2012/03/05 14:17:44 $ */ @org.junit.runner.RunWith(MixRunner.class) public class TestCase extends junit.framework.TestCase { //~ JUnit 4 rules ......................................................... /** * Applies adaptive timeout control to test methods to cope with * non-terminating methods. */ @Rule public static final AdaptiveTimeout ADAPTIVE_TIMEOUT = new AdaptiveTimeout(); //~ Instance/static variables ............................................. // These don't use the names "in" or "out" to provide better error // messages if students type those method names and accidentally leave // off the parentheses. private PrintWriterWithHistory tcOut = null; private Scanner tcIn = null; private MutableStringBufferInputStream tcInBuf = null; private StringNormalizer sn = new StringNormalizer(true); // Used for communicating with assertTrue() and assertFalse(). Ideally, // they should be instance vars, but assertTrue() and assertFalse() // have to be static so these messages must be too. private static String predicateReturnsTrueReason; private static String predicateReturnsFalseReason; private static Boolean trimStackTraces; //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates a new TestCase object. */ public TestCase() { super(); } // ---------------------------------------------------------- /** * Creates a new TestCase object. * @param name The name of this test case */ public TestCase(String name) { super(name); } //~ Methods ............................................................... // ---------------------------------------------------------- /** * Sets up the fixture, for example, open a network connection. This * method is called before each test in this class is executed. */ @Override protected void setUp() throws Exception { // Included only for Javadoc purposes--implementation adds nothing. super.setUp(); } // ---------------------------------------------------------- /** * Tears down the fixture, for example, close a network connection. * This method is called after each test in this class is executed. */ @Override protected void tearDown() throws Exception { // Included only for Javadoc purposes--implementation adds nothing. super.tearDown(); } // ---------------------------------------------------------- /** * An internal helper that ensures input/output buffering is in place * before each test case. */ protected void instrumentIO() { // Clear out all the stream history stuff tcIn = null; tcOut = null; tcInBuf = null; // Make sure these are history-wrapped SystemIOUtilities.out(); SystemIOUtilities.err(); // First, make sure the original System.in gets captured, so it // can be restored later SystemIOUtilities.replaceSystemInContents(null); // The previous line actually replaced System.in, but now we'll // "replace the replacement" with one that uses fail() if it // has no contents. System.setIn(new MutableStringBufferInputStream((String)null) { protected void handleMissingContents() { fail("Attempt to access System.in before its contents " + "have been set"); } }); } // ---------------------------------------------------------- /** * An internal helper that resets all of the input/output buffering * after each test case. */ protected void resetIO() { // Clear out all the stream history stuff tcIn = null; tcOut = null; tcInBuf = null; resetSystemIO(); } // ---------------------------------------------------------- /** * Get an output stream suitable for use in test cases. You can pass * this output stream to your own methods, and later call its * {@link student.testingsupport.PrintWriterWithHistory#getHistory()} method to * extract all the output in the form of a string. The contents of this * stream get cleared for every test case. * @return a {@link java.io.PrintWriter} suitable for use in test cases */ public PrintWriterWithHistory out() { if (tcOut == null) { tcOut = new PrintWriterWithHistory(); } return tcOut; } // ---------------------------------------------------------- /** * Get a version of {@link System#out} that records its history * so you can compare against it later. The history of this * stream gets cleared for every test case. * @return a {@link java.io.PrintStream} connected to System.out that * is suitable for use in test cases */ public PrintStreamWithHistory systemOut() { return SystemIOUtilities.out(); } // ---------------------------------------------------------- /** * Get a version of {@link System#err} that records its history * so you can compare against it later. The history of this * stream gets cleared for every test case. * @return a {@link java.io.PrintStream} connected to System.err that * is suitable for use in test cases */ public PrintStreamWithHistory systemErr() { return SystemIOUtilities.err(); } // ---------------------------------------------------------- /** * Get an input stream containing the contents that you specify. * Set the contents by calling {@link #setIn(String)} or * {@link #setIn(Scanner)} to set the contents before you begin * using this stream. This stream gets reset for every test case. * @return a {@link Scanner} containing any contents you * have specified. */ public Scanner in() { if (tcIn == null) { setIn((String)null); } return tcIn; } // ---------------------------------------------------------- /** * Set the contents of this test case's input stream, which can then * be retrieved using {@link #in()}. * @param contents The contents to use for the stream, which replace * any that were there before. */ public void setIn(String contents) { if (tcInBuf == null) { tcInBuf = new MutableStringBufferInputStream(contents) { protected void handleMissingContents() { fail("Attempt to access built-in test case Scanner " + "in() before its contents have been set"); } }; } else { tcInBuf.resetContents(contents); } // Note that this doesn't reset the existing scanner if any // code still has a reference to it. tcIn = new Scanner(tcInBuf); } // ---------------------------------------------------------- /** * Set the contents of this test case's input stream, which can then * be retrieved using {@link #in()}. * @param contents The contents to use for the stream, which replace * any that were there before. */ public void setIn(Scanner contents) { tcIn = contents; tcInBuf = null; } // ---------------------------------------------------------- /** * Set the contents of this test case's input stream, which can then * be retrieved using {@link #in()}. * @param contents The contents to use for the stream, which replace * any that were there before. */ public void setSystemIn(String contents) { SystemIOUtilities.replaceSystemInContents(contents); } // ---------------------------------------------------------- /** * Access the string normalizer that this test case uses in * fuzzy string comparisons. You can set your preferences for * fuzzy string comparisons using this object's methods. These settings * are persistent from test case method to test case method, so it is * sufficient to set them in your test class constructor if you want * to use the same settings for all of your test case methods. * @return the string normalizer * @see #assertFuzzyEquals(String, String) * @see StringNormalizer * @see student.testingsupport.StringNormalizer#addStandardRules() */ protected StringNormalizer stringNormalizer() { return sn; } // ---------------------------------------------------------- /** * Asserts that two Strings are equal, respecting preferences for what * differences matter. This method mirrors the static * {@link junit.framework.TestCase#assertEquals(String,String)} * method, augmenting its behavior with the ability to make "fuzzy" * string comparisons that ignore things like differences in spacing, * punctuation, or capitalization. Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings. * @param expected The expected value * @param actual The value to test */ public void assertFuzzyEquals(String expected, String actual) { assertFuzzyEquals(null, expected, actual); } // ---------------------------------------------------------- /** * Asserts that two Strings are equal, respecting preferences for what * differences matter. This method mirrors the static * {@link junit.framework.TestCase#assertEquals(String,String)} * method, augmenting its behavior with the ability to make "fuzzy" * string comparisons that ignore things like differences in spacing, * punctuation, or capitalization. Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings. * @param message The message to use for a failed assertion * @param expected The expected value * @param actual The value to test */ public void assertFuzzyEquals( String message, String expected, String actual) { if (message != null) { message += " (after normalizing strings)"; } try { assertEquals( message, stringNormalizer().normalize(expected), stringNormalizer().normalize(actual)); } catch (AssertionFailedError e) { trimStack(e); throw e; } } // ---------------------------------------------------------- /** * Asserts that a condition is true. If it isn't, it throws an * AssertionFailedError with the given message. This is a * special version of * {@link junit.framework.TestCase#assertTrue(String,boolean)} * that issues special diagnostics when the assertion fails, if * the given condition supports it. * @param message The message to use for a failed assertion * @param condition The condition to check */ public static void assertTrue(String message, boolean condition) { String falseReason = predicateReturnsFalseReason; predicateReturnsFalseReason = null; predicateReturnsTrueReason = null; if (falseReason != null) { if (message == null) { message = falseReason; } else { message += " " + falseReason; } } try { junit.framework.TestCase.assertTrue(message, condition); } catch (AssertionFailedError e) { trimStack(e); throw e; } } // ---------------------------------------------------------- /** * Asserts that a condition is true. If it isn't, it throws an * AssertionFailedError with the given message. This is a * special version of * {@link junit.framework.TestCase#assertTrue(boolean)} * that issues special diagnostics when the assertion fails, if * the given condition supports it. * @param condition The condition to check */ public static void assertTrue(boolean condition) { assertTrue(null, condition); } // ---------------------------------------------------------- /** * Asserts that a condition is false. If it isn't, it throws an * AssertionFailedError with the given message. This is a * special version of * {@link junit.framework.TestCase#assertFalse(String,boolean)} * that issues special diagnostics when the assertion fails, if * the given condition supports it. * @param message The message to use for a failed assertion * @param condition The condition to check */ public static void assertFalse(String message, boolean condition) { String trueReason = predicateReturnsTrueReason; predicateReturnsFalseReason = null; predicateReturnsTrueReason = null; if (trueReason != null) { if (message == null) { message = trueReason; } else { message += " " + trueReason; } } try { junit.framework.TestCase.assertFalse(message, condition); } catch (AssertionFailedError e) { trimStack(e); throw e; } } // ---------------------------------------------------------- /** * Asserts that a condition is false. If it isn't, it throws an * AssertionFailedError with the given message. This is a * special version of * {@link junit.framework.TestCase#assertFalse(boolean)} * that issues special diagnostics when the assertion fails, if * the given condition supports it. * @param condition The condition to check */ public static void assertFalse(boolean condition) { assertFalse(null, condition); } // ---------------------------------------------------------- /** * There is no assertion to compare ints with doubles, but autoboxing * will allow you to compare them as objects, which is never desired, * so this overloaded method flags the problem as a test case failure * rather than letting it go undiagnosed. * @param expected The expected value * @param actual The actual value */ public static void assertEquals(int expected, double actual) { fail("Your test case calls assertEquals() with an int (" + expected + ") and a double (" + actual + "), but comparing them directly " + "may give incorrect results. Instead, use a type cast to call " + "either assertEquals(int, int) or assertEquals(double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * There is no assertion to compare ints with doubles, but autoboxing * will allow you to compare them as objects, which is never desired, * so this overloaded method flags the problem as a test case failure * rather than letting it go undiagnosed. * @param message The message to use if the assertion fails * @param expected The expected value * @param actual The actual value */ public static void assertEquals(String message, int expected, double actual) { fail("Your test case calls assertEquals() with an int (" + expected + ") and a double (" + actual + "), but comparing them directly " + "may give incorrect results. Instead, use a type cast to call " + "either assertEquals(String, int, int) or assertEquals(String, " + "double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * There is no assertion to compare ints with doubles, but autoboxing * will allow you to compare them as objects, which is never desired, * so this overloaded method flags the problem as a test case failure * rather than letting it go undiagnosed. * @param expected The expected value * @param actual The actual value */ public static void assertEquals(double expected, int actual) { fail("Your test case calls assertEquals() with a double (" + expected + ") and an int (" + actual + "), but comparing them directly " + "may give incorrect results. Instead, use a type cast to call " + "either assertEquals(int, int) or assertEquals(double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * There is no assertion to compare ints with doubles, but autoboxing * will allow you to compare them as objects, which is never desired, * so this overloaded method flags the problem as a test case failure * rather than letting it go undiagnosed. * @param message The message to use if the assertion fails * @param expected The expected value * @param actual The actual value */ public static void assertEquals(String message, double expected, int actual) { fail("Your test case calls assertEquals() with a double (" + expected + ") and an int (" + actual + "), but comparing them directly " + "may give incorrect results. Instead, use a type cast to call " + "either assertEquals(String, int, int) or assertEquals(String, " + "double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * The assertion to compare two doubles requires three arguments, but * autoboxing will instead the wrong assertion if you only provide two * arguments. This overloaded method flags the problem as a test case * failure rather than letting it go undiagnosed. * @param expected The expected value * @param actual The actual value */ public static void assertEquals(double expected, double actual) { fail("Your test case calls assertEquals() with two doubles (" + expected + " and " + actual + "), but comparing them directly " + "may give incorrect results. Instead, use assertEquals(" + "double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * The assertion to compare two doubles requires three arguments, but * autoboxing will instead the wrong assertion if you only provide two * arguments. This overloaded method flags the problem as a test case * failure rather than letting it go undiagnosed. * @param message The message to use if the assertion fails * @param expected The expected value * @param actual The actual value */ public static void assertEquals( String message, double expected, double actual) { fail("Your test case calls assertEquals() with two doubles (" + expected + " and " + actual + "), but comparing them directly " + "may give incorrect results. Instead, use assertEquals(String, " + "double, double, " + "double). Don't forget that comparing doubles takes a third " + "argument indicating how close they have to be to be considered " + "equal."); } // ---------------------------------------------------------- /** * Takes a string and, if it is too long, shortens it by replacing the * middle with an ellipsis. For example, calling <code>compact("hello * there", 6, 3)</code> will return "hel...ere". * @param content The string to shorten * @param threshold Strings longer than this will be compacted, while * strings less than or equal to this limit will be returned * unchanged * @param prefixLen How many characters at the front and back of the * string to keep. This number must be less than or equal to half * the threshold * @return The shortened version of the string */ public static String compact(String content, int threshold, int prefixLen) { if (content != null && content.length() > threshold) { assert prefixLen < (threshold + 1) / 2; return content.substring(0, prefixLen) + "..." + content.substring(content.length() - prefixLen); } else { return content; } } // ---------------------------------------------------------- /** * Takes a string and, if it is too long, shortens it by replacing the * middle with an ellipsis. * @param content The string to shorten * @return The shortened version of the string */ public static String compact(String content) { return compact(content, 15, 5); } // ---------------------------------------------------------- /** * Determines whether two Strings are equal. This method is identical * to {@link String#equals(Object)}, but is provided for symmetry with * the other comparison predicates provided in this class. For * assertion writing, remember that * {@link junit.framework.TestCase#assertEquals(String,String)} will * produce more useful information on failure, however. * @param left The first string to compare * @param right The second string to compare * @return True if the strings are equal */ public boolean equals(String left, String right) { boolean result = left == right; if (left != null && right != null) { result = left.equals(right); } if (result) { predicateReturnsTrueReason = "<" + compact(left) + "> was the same as:<" + compact(right) + ">"; } else { String msg = (new junit.framework.ComparisonFailure(null, left, right)) .getMessage(); if (msg.startsWith("null ")) { msg = msg.substring("null ".length()); } predicateReturnsFalseReason = msg; } return result; } // ---------------------------------------------------------- /** * Determines whether two Strings are equal, respecting preferences for * what differences matter. This method mirrors * {@link #equals(String,String)}, augmenting its behavior with the * ability to make "fuzzy" string comparisons that ignore things like * differences in spacing, punctuation, or capitalization. It is also * identical to {@link #assertFuzzyEquals(String,String)}, except that it * returns the boolean result of the comparison instead of making a * test case assertion. Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings. For assertion writing, remember that * {@link #assertFuzzyEquals(String,String)} will * produce more useful information on failure, however. * @param left The first string to compare * @param right The second string to compare * @return True if the strings are equal */ public boolean fuzzyEquals(String left, String right) { return equals(stringNormalizer().normalize(left), stringNormalizer().normalize(right)); } // ---------------------------------------------------------- /** * Determines whether a String exactly matches an expected regular * expression. A null for the actual value is treated the same as an * empty string for the purposes of matching. The regular expression * must match the full string (all characters taken together). To * match a substring, use {@link #containsRegex(String,String...)} * instead. * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected pattern is the <b>second</b>. * </p> * @param actual The value to test * @param expected The expected value (interpreted as a regular * expression {@link Pattern}) * @return True if the actual matches the expected pattern */ public boolean equalsRegex(String actual, String expected) { return equalsRegex(actual, Pattern.compile(expected)); } // ---------------------------------------------------------- /** * Determines whether a String exactly matches an expected regular * expression. A null for the actual value is treated the same as an * empty string for the purposes of matching. The regular expression * must match the full string (all characters taken together). To * match a substring, use {@link #containsRegex(String,Pattern...)} * instead. * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected pattern is the <b>second</b>. * </p> * @param actual The value to test * @param expected The expected value * @return True if the actual matches the expected pattern */ public boolean equalsRegex(String actual, Pattern expected) { if (actual == null) { actual = ""; } boolean result = expected.matcher(actual).matches(); if (result) { predicateReturnsTrueReason = "<" + compact(actual) + "> matches regex:<" + compact(expected.toString(), 25, 10) + ">"; } else { predicateReturnsFalseReason = "<" + compact(actual) + "> does not match regex:<" + compact(expected.toString(), 25, 10) + ">"; } return result; } // ---------------------------------------------------------- /** * Determines whether a String exactly matches an expected regular * expression, respecting preferences for what differences matter. * A null for the actual value is treated the same as an empty string * for the purposes of matching. The regular expression must match * the full string (all characters taken together). To match a substring, * use {@link #fuzzyContainsRegex(String,String...)} instead. * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected pattern is the <b>second</b>. * </p> * <p>Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings.</p> * @param actual The value to test * @param expected The expected value (interpreted as a regular * expression {@link Pattern}) * @return True if the actual matches the expected pattern */ public boolean fuzzyEqualsRegex(String actual, String expected) { return fuzzyEqualsRegex(actual, Pattern.compile(expected)); } // ---------------------------------------------------------- /** * Determines whether a String exactly matches an expected regular * expression, respecting preferences for what differences matter. * A null for the actual value is treated the same as an empty string * for the purposes of matching. The regular expression must match * the full string (all characters taken together). To match a substring, * use {@link #fuzzyContainsRegex(String,String...)} instead. * <p>Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings.</p> * @param actual The value to test * @param expected The expected value * @return True if the actual matches the expected pattern */ public boolean fuzzyEqualsRegex(String actual, Pattern expected) { return equalsRegex(stringNormalizer().normalize(actual), expected); } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order. In addition to the string to search, you can provide an * arbitrary number of additional parameters to search for. If you only * provide one substring, this method behaves the same as * {@link String#contains(CharSequence)}. If you provide more than * one substring, it looks for each such element in turn * in the larger string, making sure they are all found in the proper order * (each substring must strictly follow the previous one, although there * can be any amount of intervening characters between any two substrings * in the array). If the larger string is null, this method returns * false (since it can contain nothing). * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected substrings are the <b>second</b>. * </p> * @param largerString The target to look in * @param substrings One or more substrings to look for (in order) * @return True if the largerString contains all of the specified * substrings in order. */ public boolean contains(String largerString, String ... substrings) { int pos = (largerString == null) ? -1 : 0; for (int i = 0; i < substrings.length && pos >= 0; i++) { pos = largerString.indexOf(substrings[i], pos); if (pos >= 0) { pos += substrings[i].length(); } else { predicateReturnsFalseReason = "<" + compact(largerString) + "> does not contain:<" + compact(substrings[i], 25, 10) + ">"; if (substrings.length > 1) { predicateReturnsFalseReason += "(substring " + i + ")"; } break; } } if (pos >= 0) { predicateReturnsTrueReason = "<" + compact(largerString) + "> contains:"; for (int i = 0; i < substrings.length; i++) { if (i > 0) { predicateReturnsTrueReason += ", "; } predicateReturnsTrueReason += "<" + compact(substrings[i], 25, 10) + ">"; } return true; } else { return false; } } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order, respecting preferences for what differences matter. It * looks for each of the specified substrings in turn * in the larger string, making sure they are all found in the proper order * (each substring must strictly follow the previous one, although there * can be any amount of intervening characters between any two substrings * in the array). If the larger string is null, this method returns * false (since it can contain nothing). * <p>This method makes "fuzzy" string comparisons that ignore things * like differences in spacing, punctuation, or capitalization. Use * {@link #stringNormalizer()} to access and modify the * {@link StringNormalizer} object's preferences for comparing * strings. * </p> * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected substrings are the <b>second</b>. * </p> * @param largerString The target to look in * @param substrings The substrings to look for (in order) * @return True if the largerString contains all of the specified * substrings in order. */ public boolean fuzzyContains(String largerString, String ... substrings) { // Normalized the array of expected substrings String[] normalizedSubstrings = new String[substrings.length]; for (int i = 0; i < substrings.length; i++) { normalizedSubstrings[i] = stringNormalizer().normalize(substrings[i]); } // Now call the regular version on the normalized args return contains( stringNormalizer().normalize(largerString), normalizedSubstrings); } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order, where the expected substrings are specified as a regular * expressions. It looks for each of the specified regular expressions * in turn in the larger string, making sure they are all found in the * proper order (each substring must strictly follow the previous one, * although there can be any amount of intervening characters between * any two substrings in the array). If the larger string is null, this * method returns false (since it can contain nothing). * <p> * Note that this predicate uses the opposite parameter ordering * from JUnit assertions: The value to test is the <b>first</b> * parameter, and the expected substrings are the <b>second</b>. * </p> * @param largerString The target to look in * @param substrings A sequence of expected substrings (interpreted as * regular expression {@link Pattern}s), which must * occur in the same order in the larger string * @return True if the largerString contains all of the specified * regular expressions in order. */ public boolean containsRegex(String largerString, String ... substrings) { Pattern[] patterns = new Pattern[substrings.length]; for (int i = 0; i < substrings.length; i++) { patterns[i] = Pattern.compile(substrings[i]); } return containsRegex(largerString, patterns); } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order, where the expected substrings are specified as a regular * expressions. It looks for each of the specified regular expressions * in turn in the larger string, making sure they are all found in the * proper order (each substring must strictly follow the previous one, * although there can be any amount of intervening characters between * any two substrings in the array). If the larger string is null, this * method returns false (since it can contain nothing). * @param largerString The target to look in * @param substrings A sequence of expected regular expressions, which * must occur in the same order in the larger string * @return True if the largerString contains all of the specified * regular expressions in order. */ public boolean containsRegex(String largerString, Pattern ... substrings) { boolean result = true; int pos = 0; for (int i = 0; i < substrings.length; i++) { Matcher matcher = substrings[i].matcher(largerString); result = matcher.find(pos); if (!result) { predicateReturnsFalseReason = "<" + compact(largerString) + "> does not contain regex:<" + compact(substrings[i].toString(), 25, 10) + ">"; if (substrings.length > 1) { predicateReturnsFalseReason += "(pattern " + i + ")"; } break; } pos = matcher.end(); } if (result) { predicateReturnsTrueReason = "<" + compact(largerString) + "> contains regexes:"; for (int i = 0; i < substrings.length; i++) { if (i > 0) { predicateReturnsTrueReason += ", "; } predicateReturnsTrueReason += "<" + compact(substrings[i].toString(), 25, 10) + ">"; } return true; } else { return false; } } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order, where the expected substrings are specified as regular * expressions, and respecting preferences for what differences matter. * This method behaves just like {@link #fuzzyContains(String,String...)}, * except that the second argument is interpreted as an array of regular * expressions. String normalization rules are only appled to the * larger string, not to the regular expressions. * @param largerString The target to look in * @param substrings An array of expected substrings (interpreted as * regular expression {@link Pattern}s), which must * occur in the same order in the larger string * @return True if the largerString contains all of the specified * substrings in order. */ public boolean fuzzyContainsRegex( String largerString, String ... substrings) { Pattern[] patterns = new Pattern[substrings.length]; for (int i = 0; i < substrings.length; i++) { patterns[i] = Pattern.compile(substrings[i]); } return fuzzyContainsRegex(largerString, patterns); } // ---------------------------------------------------------- /** * Determine whether one String contains a sequence of other substrings * in order, where the expected substrings are specified as regular * expressions, and respecting preferences for what differences matter. * This method behaves just like {@link #fuzzyContains(String,String...)}, * except that the second argument is interpreted as an array of regular * expressions. String normalization rules are only appled to the * larger string, not to the regular expressions. * @param largerString The target to look in * @param substrings An array of expected substrings, which must * occur in the same order in the larger string * @return True if the largerString contains all of the specified * substrings in order. */ public boolean fuzzyContainsRegex( String largerString, Pattern ... substrings) { return containsRegex( stringNormalizer().normalize(largerString), substrings); } // ---------------------------------------------------------- /** * Resets IO and output reasons, then instruments IO for next method. */ @Before public void prepareIO() { resetIO(); predicateReturnsTrueReason = null; predicateReturnsFalseReason = null; instrumentIO(); } // ---------------------------------------------------------- /** * An internal helper that resets the system part of the input/output * buffering after each test case. */ @AfterClass public static void resetSystemIO() { // Make sure these are history-wrapped SystemIOUtilities.out().clearHistory(); SystemIOUtilities.err().clearHistory(); SystemIOUtilities.restoreSystemIn(); } // ---------------------------------------------------------- /** * Collect timing date on the test method that just finished. */ @After public void getStats() { ADAPTIVE_TIMEOUT.logTestMethod(true); } // ---------------------------------------------------------- /** * This method is for internal use only and should not be called * by other code. It is used to hook into the data collection * mechanisms of the AdaptiveTimeout test timing control rule. */ @AfterClass public static void writeStats() { ADAPTIVE_TIMEOUT.appendStatsToFile(); } // ---------------------------------------------------------- /** * This method is for internal use only and should not be called * by other code. It is used to install System.exit() prevention * control. */ @BeforeClass public static void installExitHandler() { student.testingsupport.ExitPreventingSecurityManager.install(); } // ---------------------------------------------------------- /** * This method is for internal use only and should not be called * by other code. It is used to remove System.exit() prevention * control after the test class has been completed. */ @AfterClass public static void uninstallExitHandler() { student.testingsupport.ExitPreventingSecurityManager.uninstall(); } // ---------------------------------------------------------- private static void trimStack(Throwable t) { if (trimStackTraces == null) { try { String setting = System.getProperty("student.TestCase.trimStackTraces"); if (setting == null) { trimStackTraces = true; } else { setting = setting.toLowerCase().trim(); trimStackTraces = "yes".equals(setting) || "true".equals(setting) || "on".equals(setting) || "1".equals(setting); } } catch (Exception e) { trimStackTraces = true; } } if (!trimStackTraces) { return; } StackTraceElement[] oldTrace = t.getStackTrace(); int pos1 = 0; while (pos1 < oldTrace.length && (oldTrace[pos1].getClassName().equals("junit.framework.Assert"))) { ++pos1; } int pos2 = pos1; while (pos2 < oldTrace.length && (oldTrace[pos2].getClassName().equals("student.TestCase"))) { ++pos2; } // It would be good to strip out a top-level stack trace element for // student.TestCase.runBare(), which will come soon if (pos2 > pos1 && pos2 < oldTrace.length - 1) { StackTraceElement[] newTrace = new StackTraceElement[oldTrace.length - (pos2 - pos1)]; if (pos1 > 0) { System.arraycopy(oldTrace, 0, newTrace, 0, pos1); } System.arraycopy( oldTrace, pos2, newTrace, pos1, newTrace.length - pos1); t.setStackTrace(newTrace); } } }