diff options
Diffstat (limited to 'qpid/java/systests/src/test/java/org/apache/qpid/util')
3 files changed, 849 insertions, 0 deletions
diff --git a/qpid/java/systests/src/test/java/org/apache/qpid/util/ClasspathScanner.java b/qpid/java/systests/src/test/java/org/apache/qpid/util/ClasspathScanner.java new file mode 100644 index 0000000000..151d1473ac --- /dev/null +++ b/qpid/java/systests/src/test/java/org/apache/qpid/util/ClasspathScanner.java @@ -0,0 +1,239 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.util; + +import org.apache.log4j.Logger; + +import java.io.File; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An ClasspathScanner scans the classpath for classes that implement an interface or extend a base class and have names + * that match a regular expression. + * + * <p/>In order to test whether a class implements an interface or extends a class, the class must be loaded (unless + * the class files were to be scanned directly). Using this collector can cause problems when it scans the classpath, + * because loading classes will initialize their statics, which in turn may cause undesired side effects. For this + * reason, the collector should always be used with a regular expression, through which the class file names are + * filtered, and only those that pass this filter will be tested. For example, if you define tests in classes that + * end with the keyword "Test" then use the regular expression "Test$" to match this. + * + * <p><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Find all classes matching type and name pattern on the classpath. + * </table> + * + * @todo Add logic to scan jars as well as directories. + */ +public class ClasspathScanner +{ + private static final Logger log = Logger.getLogger(ClasspathScanner.class); + + /** + * Scans the classpath and returns all classes that extend a specified class and match a specified name. + * There is an flag that can be used to indicate that only Java Beans will be matched (that is, only those classes + * that have a default constructor). + * + * @param matchingClass The class or interface to match. + * @param matchingRegexp The regular expression to match against the class name. + * @param beanOnly Flag to indicate that onyl classes with default constructors should be matched. + * + * @return All the classes that match this collector. + */ + public static <T> Collection<Class<? extends T>> getMatches(Class<T> matchingClass, String matchingRegexp, + boolean beanOnly) + { + log.debug("public static <T> Collection<Class<? extends T>> getMatches(Class<T> matchingClass = " + matchingClass + + ", String matchingRegexp = " + matchingRegexp + ", boolean beanOnly = " + beanOnly + "): called"); + + // Build a compiled regular expression from the pattern to match. + Pattern matchPattern = Pattern.compile(matchingRegexp); + + String classPath = System.getProperty("java.class.path"); + Map<String, Class<? extends T>> result = new HashMap<String, Class<? extends T>>(); + + log.debug("classPath = " + classPath); + + // Find matching classes starting from all roots in the classpath. + for (String path : splitClassPath(classPath)) + { + gatherFiles(new File(path), "", result, matchPattern, matchingClass); + } + + return result.values(); + } + + /** + * Finds all matching classes rooted at a given location in the file system. If location is a directory it + * is recursively examined. + * + * @param classRoot The root of the current point in the file system being examined. + * @param classFileName The name of the current file or directory to examine. + * @param result The accumulated mapping from class names to classes that match the scan. + * + * @todo Recursion ok as file system depth is not likely to exhaust the stack. Might be better to replace with + * iteration. + */ + private static <T> void gatherFiles(File classRoot, String classFileName, Map<String, Class<? extends T>> result, + Pattern matchPattern, Class<? extends T> matchClass) + { + log.debug("private static <T> void gatherFiles(File classRoot = " + classRoot + ", String classFileName = " + + classFileName + ", Map<String, Class<? extends T>> result, Pattern matchPattern = " + matchPattern + + ", Class<? extends T> matchClass = " + matchClass + "): called"); + + File thisRoot = new File(classRoot, classFileName); + + // If the current location is a file, check if it is a matching class. + if (thisRoot.isFile()) + { + // Check that the file has a matching name. + if (matchesName(thisRoot.getName(), matchPattern)) + { + String className = classNameFromFile(thisRoot.getName()); + + // Check that the class has matching type. + try + { + Class<?> candidateClass = Class.forName(className); + + Class matchedClass = matchesClass(candidateClass, matchClass); + + if (matchedClass != null) + { + result.put(className, matchedClass); + } + } + catch (ClassNotFoundException e) + { + // Ignore this. The matching class could not be loaded. + log.debug("Got ClassNotFoundException, ignoring.", e); + } + } + + return; + } + // Otherwise the current location is a directory, so examine all of its contents. + else + { + String[] contents = thisRoot.list(); + + if (contents != null) + { + for (String content : contents) + { + gatherFiles(classRoot, classFileName + File.separatorChar + content, result, matchPattern, matchClass); + } + } + } + } + + /** + * Checks if the specified class file name corresponds to a class with name matching the specified regular expression. + * + * @param classFileName The class file name. + * @param matchPattern The regular expression pattern to match. + * + * @return <tt>true</tt> if the class name matches, <tt>false</tt> otherwise. + */ + private static boolean matchesName(String classFileName, Pattern matchPattern) + { + String className = classNameFromFile(classFileName); + Matcher matcher = matchPattern.matcher(className); + + return matcher.matches(); + } + + /** + * Checks if the specified class to compare extends the base class being scanned for. + * + * @param matchingClass The base class to match against. + * @param toMatch The class to match against the base class. + * + * @return The class to check, cast as an instance of the class to match if the class extends the base class, or + * <tt>null</tt> otherwise. + */ + private static <T> Class<? extends T> matchesClass(Class<?> matchingClass, Class<? extends T> toMatch) + { + try + { + return matchingClass.asSubclass(toMatch); + } + catch (ClassCastException e) + { + return null; + } + } + + /** + * Takes a classpath (which is a series of paths) and splits it into its component paths. + * + * @param classPath The classpath to split. + * + * @return A list of the component paths that make up the class path. + */ + private static List<String> splitClassPath(String classPath) + { + List<String> result = new LinkedList<String>(); + String separator = System.getProperty("path.separator"); + StringTokenizer tokenizer = new StringTokenizer(classPath, separator); + + while (tokenizer.hasMoreTokens()) + { + result.add(tokenizer.nextToken()); + } + + return result; + } + + /** + * Translates from the filename of a class to its fully qualified classname. Files are named using forward slash + * seperators and end in ".class", whereas fully qualified class names use "." sperators and no ".class" ending. + * + * @param classFileName The filename of the class to translate to a class name. + * + * @return The fully qualified class name. + */ + private static String classNameFromFile(String classFileName) + { + log.debug("private static String classNameFromFile(String classFileName = " + classFileName + "): called"); + + // Remove the .class ending. + String s = classFileName.substring(0, classFileName.length() - ".class".length()); + + // Turn / seperators in . seperators. + String s2 = s.replace(File.separatorChar, '.'); + + // Knock off any leading . caused by a leading /. + if (s2.startsWith(".")) + { + return s2.substring(1); + } + + return s2; + } +} diff --git a/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitor.java b/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitor.java new file mode 100644 index 0000000000..d77731d09f --- /dev/null +++ b/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitor.java @@ -0,0 +1,336 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.util; + +import org.apache.log4j.FileAppender; +import org.apache.log4j.Logger; +import org.apache.log4j.SimpleLayout; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.LineNumberReader; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Utility to simplify the monitoring of Log4j file output + * + * Monitoring of a given log file can be done alternatively the Monitor will + * add a new log4j FileAppender to the root Logger to gather all the available + * logging for monitoring + */ +public class LogMonitor +{ + private static final Logger _logger = Logger.getLogger(LogMonitor.class); + + // The file that the log statements will be written to. + private final File _logfile; + + // The appender we added to the get messages + private final FileAppender _appender; + + private int _linesToSkip = 0; + + /** + * Create a new LogMonitor that creates a new Log4j Appender and monitors + * all log4j output via the current configuration. + * + * @throws IOException if there is a problem creating the temporary file. + */ + public LogMonitor() throws IOException + { + this(null); + } + + /** + * Create a new LogMonitor on the specified file if the file does not exist + * or the value is null then a new Log4j appender will be added and + * monitoring set up on that appender. + * + * NOTE: for the appender to receive any value the RootLogger will need to + * have the level correctly configured.ng + * + * @param file the file to monitor + * + * @throws IOException if there is a problem creating a temporary file + */ + public LogMonitor(File file) throws IOException + { + if (file != null && file.exists()) + { + _logfile = file; + _appender = null; + } + else + { + // This is mostly for running the test outside of the ant setup + _logfile = File.createTempFile("LogMonitor", ".log"); + _appender = new FileAppender(new SimpleLayout(), + _logfile.getAbsolutePath()); + _appender.setFile(_logfile.getAbsolutePath()); + _appender.setImmediateFlush(true); + Logger.getRootLogger().addAppender(_appender); + } + + _logger.info("Created LogMonitor. Monitoring file: " + _logfile.getAbsolutePath()); + } + + /** + * Checks the log file for a given message to appear and returns all + * instances of that appearance. + * + * @param message the message to wait for in the log + * @param wait the time in ms to wait for the message to occur + * @return true if the message was found + * + * @throws java.io.FileNotFoundException if the Log file can no longer be found + * @throws IOException thrown when reading the log file + */ + public List<String> waitAndFindMatches(String message, long wait) + throws FileNotFoundException, IOException + { + if (waitForMessage(message, wait)) + { + return findMatches(message); + } + else + { + return new LinkedList<String>(); + } + } + + /** + * Checks the log for instances of the search string. If the caller + * has previously called {@link #markDiscardPoint()}, lines up until the discard + * point are not considered. + * + * The pattern parameter can take any valid argument used in String.contains() + * + * {@see String.contains(CharSequences)} + * + * @param pattern the search string + * + * @return a list of matching lines from the log + * + * @throws IOException if there is a problem with the file + */ + public List<String> findMatches(String pattern) throws IOException + { + + List<String> results = new LinkedList<String>(); + + LineNumberReader reader = new LineNumberReader(new FileReader(_logfile)); + try + { + while (reader.ready()) + { + String line = reader.readLine(); + if (reader.getLineNumber() > _linesToSkip && line.contains(pattern)) + { + results.add(line); + } + } + } + finally + { + reader.close(); + } + + return results; + } + + public Map<String, List<String>> findMatches(String... pattern) throws IOException + { + + Map<String, List<String>> results= new HashMap<String, List<String>>(); + for (String p : pattern) + { + results.put(p, new LinkedList<String>()); + } + LineNumberReader reader = new LineNumberReader(new FileReader(_logfile)); + try + { + while (reader.ready()) + { + String line = reader.readLine(); + if (reader.getLineNumber() > _linesToSkip) + { + for (String p : pattern) + { + if (line.contains(p)) + { + results.get(p).add(line); + } + } + } + } + } + finally + { + reader.close(); + } + + return results; + } + /** + * Checks the log file for a given message to appear. If the caller + * has previously called {@link #markDiscardPoint()}, lines up until the discard + * point are not considered. + * + * @param message the message to wait for in the log + * @param wait the time in ms to wait for the message to occur + * @return true if the message was found + * + * @throws java.io.FileNotFoundException if the Log file can no longer be found + * @throws IOException thrown when reading the log file + */ + public boolean waitForMessage(String message, long wait) + throws FileNotFoundException, IOException + { + // Loop through alerts until we're done or wait ms seconds have passed, + // just in case the logfile takes a while to flush. + LineNumberReader reader = null; + try + { + reader = new LineNumberReader(new FileReader(_logfile)); + + boolean found = false; + long endtime = System.currentTimeMillis() + wait; + while (!found && System.currentTimeMillis() < endtime) + { + boolean ready = true; + while (ready = reader.ready()) + { + String line = reader.readLine(); + + if (reader.getLineNumber() > _linesToSkip) + { + if (line.contains(message)) + { + found = true; + break; + } + } + } + if (!ready) + { + try + { + Thread.sleep(50); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + } + return found; + + } + finally + { + if (reader != null) + { + reader.close(); + } + } + } + + /** + * Read the log file in to memory as a String + * + * @return the current contents of the log file + * + * @throws java.io.FileNotFoundException if the Log file can no longer be found + * @throws IOException thrown when reading the log file + */ + public String readFile() throws FileNotFoundException, IOException + { + return FileUtils.readFileAsString(_logfile); + } + + /** + * Return a File reference to the monitored file + * + * @return the file being monitored + */ + public File getMonitoredFile() + { + return _logfile; + } + + /** + * Marks the discard point in the log file. + * + * @throws java.io.FileNotFoundException if the Log file can no longer be found + * @throws IOException thrown if there is a problem with the log file + */ + public void markDiscardPoint() throws FileNotFoundException, IOException + { + _linesToSkip = countLinesInFile(); + } + + private int countLinesInFile() throws IOException + { + int lineCount = 0; + BufferedReader br = null; + try + { + br = new BufferedReader(new FileReader(_logfile)); + while(br.readLine() != null) + { + lineCount++; + } + + return lineCount; + } + finally + { + if (br != null) + { + br.close(); + } + } + } + + /** + * Stop monitoring this file. + * + * This is required to be called incase we added a new logger. + * + * If we don't call close then the new logger will continue to get log entries + * after our desired test has finished. + */ + public void close() + { + //Remove the custom appender we added for this logger + if (_appender != null) + { + Logger.getRootLogger().removeAppender(_appender); + } + } + +} diff --git a/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitorTest.java b/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitorTest.java new file mode 100644 index 0000000000..89f707fbef --- /dev/null +++ b/qpid/java/systests/src/test/java/org/apache/qpid/util/LogMonitorTest.java @@ -0,0 +1,274 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.qpid.util; + +import junit.framework.TestCase; +import org.apache.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class LogMonitorTest extends TestCase +{ + + private LogMonitor _monitor; + + @Override + public void setUp() throws Exception + { + _monitor = new LogMonitor(); + _monitor.getMonitoredFile().deleteOnExit(); // Make sure we clean up + } + + /** + * Test that a new file is created when attempting to set up a monitor with + * the default constructor. + */ + public void testMonitor() + { + //Validate that the monitor is now running on a new file + assertTrue("New file does not have correct name:" + _monitor. + getMonitoredFile().getName(), + _monitor.getMonitoredFile().getName().contains("LogMonitor")); + } + + /** + * Test that creation of a monitor on an existing file is possible + * + * This also tests taht getMonitoredFile works + * + * @throws IOException if there is a problem creating the temporary file + */ + public void testMonitorNormalFile() throws IOException + { + File testFile = File.createTempFile("testMonitorFile", ".log"); + testFile.deleteOnExit(); + + //Ensure that we can create a monitor on a file + try + { + _monitor = new LogMonitor(testFile); + assertEquals(testFile, _monitor.getMonitoredFile()); + } + catch (IOException ioe) + { + fail("IOE thrown:" + ioe); + } + + } + + /** + * Test that a new file is created when attempting to set up a monitor on + * a null input value. + */ + public void testMonitorNullFile() + { + // Validate that a NPE is thrown with null input + try + { + LogMonitor montior = new LogMonitor(null); + //Validte that the monitor is now running on a new file + assertTrue("New file does not have correct name:" + montior. + getMonitoredFile().getName(), + montior.getMonitoredFile().getName().contains("LogMonitor")); + } + catch (IOException ioe) + { + fail("IOE thrown:" + ioe); + } + } + + /** + * Test that a new file is created when attempting to set up a monitor on + * a non existing file. + * + * @throws IOException if there is a problem setting up the nonexistent file + */ + public void testMonitorNonExistentFile() throws IOException + { + //Validate that we get a FileNotFound if the file does not exist + + File nonexist = File.createTempFile("nonexist", ".out"); + + assertTrue("Unable to delete file for our test", nonexist.delete()); + + assertFalse("Unable to test as our test file exists.", nonexist.exists()); + + try + { + LogMonitor montior = new LogMonitor(nonexist); + //Validte that the monitor is now running on a new file + assertTrue("New file does not have correct name:" + montior. + getMonitoredFile().getName(), + montior.getMonitoredFile().getName().contains("LogMonitor")); + } + catch (IOException ioe) + { + fail("IOE thrown:" + ioe); + } + } + + /** + * Test that Log file matches logged messages. + * + * @throws java.io.IOException if there is a problem creating LogMontior + */ + public void testFindMatches_Match() throws IOException + { + + String message = getName() + ": Test Message"; + + Logger.getRootLogger().warn(message); + + validateLogContainsMessage(_monitor, message); + } + + /** + * Test that Log file does not match a message not logged. + * + * @throws java.io.IOException if there is a problem creating LogMontior + */ + public void testFindMatches_NoMatch() throws IOException + { + String message = getName() + ": Test Message"; + + Logger.getRootLogger().warn(message); + + String notLogged = "This text was not logged"; + + validateLogDoesNotContainMessage(_monitor, notLogged); + } + + public void testWaitForMessage_Timeout() throws IOException + { + String message = getName() + ": Test Message"; + + long TIME_OUT = 2000; + + logMessageWithDelay(message, TIME_OUT); + + // Verify that we can time out waiting for a message + assertFalse("Message was logged ", + _monitor.waitForMessage(message, TIME_OUT / 2)); + + // Verify that the message did eventually get logged. + assertTrue("Message was never logged.", + _monitor.waitForMessage(message, TIME_OUT)); + } + + public void testDiscardPoint() throws IOException + { + String firstMessage = getName() + ": Test Message1"; + Logger.getRootLogger().warn(firstMessage); + + validateLogContainsMessage(_monitor, firstMessage); + + _monitor.markDiscardPoint(); + + validateLogDoesNotContainMessage(_monitor, firstMessage); + + String secondMessage = getName() + ": Test Message2"; + Logger.getRootLogger().warn(secondMessage); + validateLogContainsMessage(_monitor, secondMessage); + } + + public void testRead() throws IOException + { + String message = getName() + ": Test Message"; + + Logger.getRootLogger().warn(message); + + String fileContents = _monitor.readFile(); + + assertTrue("Logged message not found when reading file.", + fileContents.contains(message)); + } + + /****************** Helpers ******************/ + + /** + * Validate that the LogMonitor does not match the given string in the log + * + * @param log The LogMonitor to check + * @param message The message to check for + * + * @throws IOException if a problems occurs + */ + protected void validateLogDoesNotContainMessage(LogMonitor log, String message) + throws IOException + { + List<String> results = log.findMatches(message); + + assertNotNull("Null results returned.", results); + + assertEquals("Incorrect result set size", 0, results.size()); + } + + /** + * Validate that the LogMonitor can match the given string in the log + * + * @param log The LogMonitor to check + * @param message The message to check for + * + * @throws IOException if a problems occurs + */ + protected void validateLogContainsMessage(LogMonitor log, String message) + throws IOException + { + List<String> results = log.findMatches(message); + + assertNotNull("Null results returned.", results); + + assertEquals("Incorrect result set size", 1, results.size()); + + assertTrue("Logged Message'" + message + "' not present in results:" + + results.get(0), results.get(0).contains(message)); + } + + /** + * Create a new thread to log the given message after the set delay + * + * @param message the messasge to log + * @param delay the delay (ms) to wait before logging + */ + private void logMessageWithDelay(final String message, final long delay) + { + new Thread(new Runnable() + { + + public void run() + { + try + { + Thread.sleep(delay); + } + catch (InterruptedException e) + { + //ignore + } + + Logger.getRootLogger().warn(message); + } + }).start(); + } + +} |