/* * reserved comment block * DO NOT REMOVE OR ALTER! */ /* * Copyright 1999-2004 The Apache Software Foundation. * * 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 com.sun.org.apache.regexp.internal; import java.io.BufferedReader; import java.io.FileReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.File; import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; import java.io.StringBufferInputStream; import java.io.StringReader; import java.io.IOException; /** * Data driven (and optionally interactive) testing harness to exercise regular * expression compiler and matching engine. * * @author <a href="mailto:jonl@muppetlabs.com">Jonathan Locke</a> * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a> * @author <a href="mailto:gholam@xtra.co.nz">Michael McCallum</a> * @version $Id: RETest.java,v 1.1.2.1 2005/08/01 00:02:57 jeffsuttor Exp $ */ public class RETest { // True if we want to see output from success cases static final boolean showSuccesses = false; // A new line character. static final String NEW_LINE = System.getProperty( "line.separator" ); // Construct a debug compiler REDebugCompiler compiler = new REDebugCompiler(); /** * Main program entrypoint. If an argument is given, it will be compiled * and interactive matching will ensue. If no argument is given, the * file RETest.txt will be used as automated testing input. * @param args Command line arguments (optional regular expression) */ public static void main(String[] args) { try { if (!test( args )) { System.exit(1); } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } /** * Testing entrypoint. * @param args Command line arguments * @exception Exception thrown in case of error */ public static boolean test( String[] args ) throws Exception { RETest test = new RETest(); // Run interactive tests against a single regexp if (args.length == 2) { test.runInteractiveTests(args[1]); } else if (args.length == 1) { // Run automated tests test.runAutomatedTests(args[0]); } else { System.out.println( "Usage: RETest ([-i] [regex]) ([/path/to/testfile.txt])" ); System.out.println( "By Default will run automated tests from file 'docs/RETest.txt' ..." ); System.out.println(); test.runAutomatedTests("docs/RETest.txt"); } return test.failures == 0; } /** * Constructor */ public RETest() { } /** * Compile and test matching against a single expression * @param expr Expression to compile and test */ void runInteractiveTests(String expr) { RE r = new RE(); try { // Compile expression r.setProgram(compiler.compile(expr)); // Show expression say("" + NEW_LINE + "" + expr + "" + NEW_LINE + ""); // Show program for compiled expression PrintWriter writer = new PrintWriter( System.out ); compiler.dumpProgram( writer ); writer.flush(); boolean running = true; // Test matching against compiled expression while ( running ) { // Read from keyboard BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("> "); System.out.flush(); String match = br.readLine(); if ( match != null ) { // Try a match against the keyboard input if (r.match(match)) { say("Match successful."); } else { say("Match failed."); } // Show subparen registers showParens(r); } else { running = false; System.out.println(); } } } catch (Exception e) { say("Error: " + e.toString()); e.printStackTrace(); } } /** * Exit with a fatal error. * @param s Last famous words before exiting */ void die(String s) { say("FATAL ERROR: " + s); System.exit(-1); } /** * Fail with an error. Will print a big failure message to System.out. * * @param log Output before failure * @param s Failure description */ void fail(StringBuffer log, String s) { System.out.print(log.toString()); fail(s); } /** * Fail with an error. Will print a big failure message to System.out. * * @param s Failure description */ void fail(String s) { failures++; say("" + NEW_LINE + ""); say("*******************************************************"); say("********************* FAILURE! **********************"); say("*******************************************************"); say("" + NEW_LINE + ""); say(s); say(""); // make sure the writer gets flushed. if (compiler != null) { PrintWriter writer = new PrintWriter( System.out ); compiler.dumpProgram( writer ); writer.flush(); say("" + NEW_LINE + ""); } } /** * Say something to standard out * @param s What to say */ void say(String s) { System.out.println(s); } /** * Dump parenthesized subexpressions found by a regular expression matcher object * @param r Matcher object with results to show */ void showParens(RE r) { // Loop through each paren for (int i = 0; i < r.getParenCount(); i++) { // Show paren register say("$" + i + " = " + r.getParen(i)); } } /* * number in automated test */ int testCount = 0; /* * Count of failures in automated test */ int failures = 0; /** * Run automated tests in RETest.txt file (from Perl 4.0 test battery) * @exception Exception thrown in case of error */ void runAutomatedTests(String testDocument) throws Exception { long ms = System.currentTimeMillis(); // Some unit tests testPrecompiledRE(); testSplitAndGrep(); testSubst(); testOther(); // Test from script file File testInput = new File(testDocument); if (! testInput.exists()) { throw new Exception ("Could not find: " + testDocument); } BufferedReader br = new BufferedReader(new FileReader(testInput)); try { // While input is available, parse lines while (br.ready()) { RETestCase testcase = getNextTestCase(br); if (testcase != null) { testcase.runTest(); } } } finally { br.close(); } // Show match time say(NEW_LINE + NEW_LINE + "Match time = " + (System.currentTimeMillis() - ms) + " ms."); // Print final results if (failures > 0) { say("*************** THERE ARE FAILURES! *******************"); } say("Tests complete. " + testCount + " tests, " + failures + " failure(s)."); } /** * Run automated unit test * @exception Exception thrown in case of error */ void testOther() throws Exception { // Serialization test 1: Compile regexp and serialize/deserialize it RE r = new RE("(a*)b"); say("Serialized/deserialized (a*)b"); ByteArrayOutputStream out = new ByteArrayOutputStream(128); new ObjectOutputStream(out).writeObject(r); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); r = (RE)new ObjectInputStream(in).readObject(); if (!r.match("aaab")) { fail("Did not match 'aaab' with deserialized RE."); } else { say("aaaab = true"); showParens(r); } // Serialization test 2: serialize/deserialize used regexp out.reset(); say("Deserialized (a*)b"); new ObjectOutputStream(out).writeObject(r); in = new ByteArrayInputStream(out.toByteArray()); r = (RE)new ObjectInputStream(in).readObject(); if (r.getParenCount() != 0) { fail("Has parens after deserialization."); } if (!r.match("aaab")) { fail("Did not match 'aaab' with deserialized RE."); } else { say("aaaab = true"); showParens(r); } // Test MATCH_CASEINDEPENDENT r = new RE("abc(\\w*)"); say("MATCH_CASEINDEPENDENT abc(\\w*)"); r.setMatchFlags(RE.MATCH_CASEINDEPENDENT); say("abc(d*)"); if (!r.match("abcddd")) { fail("Did not match 'abcddd'."); } else { say("abcddd = true"); showParens(r); } if (!r.match("aBcDDdd")) { fail("Did not match 'aBcDDdd'."); } else { say("aBcDDdd = true"); showParens(r); } if (!r.match("ABCDDDDD")) { fail("Did not match 'ABCDDDDD'."); } else { say("ABCDDDDD = true"); showParens(r); } r = new RE("(A*)b\\1"); r.setMatchFlags(RE.MATCH_CASEINDEPENDENT); if (!r.match("AaAaaaBAAAAAA")) { fail("Did not match 'AaAaaaBAAAAAA'."); } else { say("AaAaaaBAAAAAA = true"); showParens(r); } r = new RE("[A-Z]*"); r.setMatchFlags(RE.MATCH_CASEINDEPENDENT); if (!r.match("CaBgDe12")) { fail("Did not match 'CaBgDe12'."); } else { say("CaBgDe12 = true"); showParens(r); } // Test MATCH_MULTILINE. Test for eol/bol symbols. r = new RE("^abc$", RE.MATCH_MULTILINE); if (!r.match("\nabc")) { fail("\"\\nabc\" doesn't match \"^abc$\""); } if (!r.match("\rabc")) { fail("\"\\rabc\" doesn't match \"^abc$\""); } if (!r.match("\r\nabc")) { fail("\"\\r\\nabc\" doesn't match \"^abc$\""); } if (!r.match("\u0085abc")) { fail("\"\\u0085abc\" doesn't match \"^abc$\""); } if (!r.match("\u2028abc")) { fail("\"\\u2028abc\" doesn't match \"^abc$\""); } if (!r.match("\u2029abc")) { fail("\"\\u2029abc\" doesn't match \"^abc$\""); } // Test MATCH_MULTILINE. Test that '.' does not matches new line. r = new RE("^a.*b$", RE.MATCH_MULTILINE); if (r.match("a\nb")) { fail("\"a\\nb\" matches \"^a.*b$\""); } if (r.match("a\rb")) { fail("\"a\\rb\" matches \"^a.*b$\""); } if (r.match("a\r\nb")) { fail("\"a\\r\\nb\" matches \"^a.*b$\""); } if (r.match("a\u0085b")) { fail("\"a\\u0085b\" matches \"^a.*b$\""); } if (r.match("a\u2028b")) { fail("\"a\\u2028b\" matches \"^a.*b$\""); } if (r.match("a\u2029b")) { fail("\"a\\u2029b\" matches \"^a.*b$\""); } } private void testPrecompiledRE() { // Pre-compiled regular expression "a*b" char[] re1Instructions = { 0x007c, 0x0000, 0x001a, 0x007c, 0x0000, 0x000d, 0x0041, 0x0001, 0x0004, 0x0061, 0x007c, 0x0000, 0x0003, 0x0047, 0x0000, 0xfff6, 0x007c, 0x0000, 0x0003, 0x004e, 0x0000, 0x0003, 0x0041, 0x0001, 0x0004, 0x0062, 0x0045, 0x0000, 0x0000, }; REProgram re1 = new REProgram(re1Instructions); // Simple test of pre-compiled regular expressions RE r = new RE(re1); say("a*b"); boolean result = r.match("aaab"); say("aaab = " + result); showParens(r); if (!result) { fail("\"aaab\" doesn't match to precompiled \"a*b\""); } result = r.match("b"); say("b = " + result); showParens(r); if (!result) { fail("\"b\" doesn't match to precompiled \"a*b\""); } result = r.match("c"); say("c = " + result); showParens(r); if (result) { fail("\"c\" matches to precompiled \"a*b\""); } result = r.match("ccccaaaaab"); say("ccccaaaaab = " + result); showParens(r); if (!result) { fail("\"ccccaaaaab\" doesn't match to precompiled \"a*b\""); } } private void testSplitAndGrep() { String[] expected = {"xxxx", "xxxx", "yyyy", "zzz"}; RE r = new RE("a*b"); String[] s = r.split("xxxxaabxxxxbyyyyaaabzzz"); for (int i = 0; i < expected.length && i < s.length; i++) { assertEquals("Wrong splitted part", expected[i], s[i]); } assertEquals("Wrong number of splitted parts", expected.length, s.length); r = new RE("x+"); expected = new String[] {"xxxx", "xxxx"}; s = r.grep(s); for (int i = 0; i < s.length; i++) { say("s[" + i + "] = " + s[i]); assertEquals("Grep fails", expected[i], s[i]); } assertEquals("Wrong number of string found by grep", expected.length, s.length); } private void testSubst() { RE r = new RE("a*b"); String expected = "-foo-garply-wacky-"; String actual = r.subst("aaaabfooaaabgarplyaaabwackyb", "-"); assertEquals("Wrong result of substitution in \"a*b\"", expected, actual); // Test subst() with backreferences r = new RE("http://[\\.\\w\\-\\?/~_@&=%]+"); actual = r.subst("visit us: http://www.apache.org!", "1234<a href=\"$0\">$0</a>", RE.REPLACE_BACKREFERENCES); assertEquals("Wrong subst() result", "visit us: 1234<a href=\"http://www.apache.org\">http://www.apache.org</a>!", actual); // Test subst() with backreferences without leading characters // before first backreference r = new RE("(.*?)=(.*)"); actual = r.subst("variable=value", "$1_test_$212", RE.REPLACE_BACKREFERENCES); assertEquals("Wrong subst() result", "variable_test_value12", actual); // Test subst() with NO backreferences r = new RE("^a$"); actual = r.subst("a", "b", RE.REPLACE_BACKREFERENCES); assertEquals("Wrong subst() result", "b", actual); // Test subst() with NO backreferences r = new RE("^a$", RE.MATCH_MULTILINE); actual = r.subst("\r\na\r\n", "b", RE.REPLACE_BACKREFERENCES); assertEquals("Wrong subst() result", "\r\nb\r\n", actual); } public void assertEquals(String message, String expected, String actual) { if (expected != null && !expected.equals(actual) || actual != null && !actual.equals(expected)) { fail(message + " (expected \"" + expected + "\", actual \"" + actual + "\")"); } } public void assertEquals(String message, int expected, int actual) { if (expected != actual) { fail(message + " (expected \"" + expected + "\", actual \"" + actual + "\")"); } } /** * Converts yesno string to boolean. * @param yesno string representation of expected result * @return true if yesno is "YES", false if yesno is "NO" * stops program otherwise. */ private boolean getExpectedResult(String yesno) { if ("NO".equals(yesno)) { return false; } else if ("YES".equals(yesno)) { return true; } else { // Bad test script die("Test script error!"); return false; //to please javac } } /** * Finds next test description in a given script. * @param br <code>BufferedReader</code> for a script file * @return strign tag for next test description * @exception IOException if some io problems occured */ private String findNextTest(BufferedReader br) throws IOException { String number = ""; while (br.ready()) { number = br.readLine(); if (number == null) { break; } number = number.trim(); if (number.startsWith("#")) { break; } if (!number.equals("")) { say("Script error. Line = " + number); System.exit(-1); } } return number; } /** * Creates testcase for the next test description in the script file. * @param br <code>BufferedReader</code> for script file. * @return a new tescase or null. * @exception IOException if some io problems occured */ private RETestCase getNextTestCase(BufferedReader br) throws IOException { // Find next re test case final String tag = findNextTest(br); // Are we done? if (!br.ready()) { return null; } // Get expression final String expr = br.readLine(); // Get test information final String matchAgainst = br.readLine(); final boolean badPattern = "ERR".equals(matchAgainst); boolean shouldMatch = false; int expectedParenCount = 0; String[] expectedParens = null; if (!badPattern) { shouldMatch = getExpectedResult(br.readLine().trim()); if (shouldMatch) { expectedParenCount = Integer.parseInt(br.readLine().trim()); expectedParens = new String[expectedParenCount]; for (int i = 0; i < expectedParenCount; i++) { expectedParens[i] = br.readLine(); } } } return new RETestCase(this, tag, expr, matchAgainst, badPattern, shouldMatch, expectedParens); } } final class RETestCase { final private StringBuffer log = new StringBuffer(); final private int number; final private String tag; // number from script file final private String pattern; final private String toMatch; final private boolean badPattern; final private boolean shouldMatch; final private String[] parens; final private RETest test; private RE regexp; public RETestCase(RETest test, String tag, String pattern, String toMatch, boolean badPattern, boolean shouldMatch, String[] parens) { this.number = ++test.testCount; this.test = test; this.tag = tag; this.pattern = pattern; this.toMatch = toMatch; this.badPattern = badPattern; this.shouldMatch = shouldMatch; if (parens != null) { this.parens = new String[parens.length]; for (int i = 0; i < parens.length; i++) { this.parens[i] = parens[i]; } } else { this.parens = null; } } public void runTest() { test.say(tag + "(" + number + "): " + pattern); if (testCreation()) { testMatch(); } } boolean testCreation() { try { // Compile it regexp = new RE(); regexp.setProgram(test.compiler.compile(pattern)); // Expression didn't cause an expected error if (badPattern) { test.fail(log, "Was expected to be an error, but wasn't."); return false; } return true; } // Some expressions *should* cause exceptions to be thrown catch (Exception e) { // If it was supposed to be an error, report success and continue if (badPattern) { log.append(" Match: ERR\n"); success("Produces an error (" + e.toString() + "), as expected."); return false; } // Wasn't supposed to be an error String message = (e.getMessage() == null) ? e.toString() : e.getMessage(); test.fail(log, "Produces an unexpected exception \"" + message + "\""); e.printStackTrace(); } catch (Error e) { // Internal error happened test.fail(log, "Compiler threw fatal error \"" + e.getMessage() + "\""); e.printStackTrace(); } return false; } private void testMatch() { log.append(" Match against: '" + toMatch + "'\n"); // Try regular matching try { // Match against the string boolean result = regexp.match(toMatch); log.append(" Matched: " + (result ? "YES" : "NO") + "\n"); // Check result, parens, and iterators if (checkResult(result) && (!shouldMatch || checkParens())) { // test match(CharacterIterator, int) // for every CharacterIterator implementation. log.append(" Match using StringCharacterIterator\n"); if (!tryMatchUsingCI(new StringCharacterIterator(toMatch))) return; log.append(" Match using CharacterArrayCharacterIterator\n"); if (!tryMatchUsingCI(new CharacterArrayCharacterIterator(toMatch.toCharArray(), 0, toMatch.length()))) return; log.append(" Match using StreamCharacterIterator\n"); if (!tryMatchUsingCI(new StreamCharacterIterator(new StringBufferInputStream(toMatch)))) return; log.append(" Match using ReaderCharacterIterator\n"); if (!tryMatchUsingCI(new ReaderCharacterIterator(new StringReader(toMatch)))) return; } } // Matcher blew it catch(Exception e) { test.fail(log, "Matcher threw exception: " + e.toString()); e.printStackTrace(); } // Internal error catch(Error e) { test.fail(log, "Matcher threw fatal error \"" + e.getMessage() + "\""); e.printStackTrace(); } } private boolean checkResult(boolean result) { // Write status if (result == shouldMatch) { success((shouldMatch ? "Matched" : "Did not match") + " \"" + toMatch + "\", as expected:"); return true; } else { if (shouldMatch) { test.fail(log, "Did not match \"" + toMatch + "\", when expected to."); } else { test.fail(log, "Matched \"" + toMatch + "\", when not expected to."); } return false; } } private boolean checkParens() { // Show subexpression registers if (RETest.showSuccesses) { test.showParens(regexp); } log.append(" Paren count: " + regexp.getParenCount() + "\n"); if (!assertEquals(log, "Wrong number of parens", parens.length, regexp.getParenCount())) { return false; } // Check registers against expected contents for (int p = 0; p < regexp.getParenCount(); p++) { log.append(" Paren " + p + ": " + regexp.getParen(p) + "\n"); // Compare expected result with actual if ("null".equals(parens[p]) && regexp.getParen(p) == null) { // Consider "null" in test file equal to null continue; } if (!assertEquals(log, "Wrong register " + p, parens[p], regexp.getParen(p))) { return false; } } return true; } boolean tryMatchUsingCI(CharacterIterator matchAgainst) { try { boolean result = regexp.match(matchAgainst, 0); log.append(" Match: " + (result ? "YES" : "NO") + "\n"); return checkResult(result) && (!shouldMatch || checkParens()); } // Matcher blew it catch(Exception e) { test.fail(log, "Matcher threw exception: " + e.toString()); e.printStackTrace(); } // Internal error catch(Error e) { test.fail(log, "Matcher threw fatal error \"" + e.getMessage() + "\""); e.printStackTrace(); } return false; } public boolean assertEquals(StringBuffer log, String message, String expected, String actual) { if (expected != null && !expected.equals(actual) || actual != null && !actual.equals(expected)) { test.fail(log, message + " (expected \"" + expected + "\", actual \"" + actual + "\")"); return false; } return true; } public boolean assertEquals(StringBuffer log, String message, int expected, int actual) { if (expected != actual) { test.fail(log, message + " (expected \"" + expected + "\", actual \"" + actual + "\")"); return false; } return true; } /** * Show a success * @param s Success story */ void success(String s) { if (RETest.showSuccesses) { test.say("" + RETest.NEW_LINE + "-----------------------" + RETest.NEW_LINE + ""); test.say("Expression #" + (number) + " \"" + pattern + "\" "); test.say("Success: " + s); } } }