Index: src/core/org/apache/jmeter/resources/messages.properties =================================================================== --- src/core/org/apache/jmeter/resources/messages.properties (revision 813160) +++ src/core/org/apache/jmeter/resources/messages.properties (working copy) @@ -406,6 +406,7 @@ junit_failure_msg=Failure Message junit_pkg_filter=Package Filter junit_request=JUnit Request +junit_4_request=JUnit Annotated Request junit_request_defaults=JUnit Request Defaults junit_success_code=Success Code junit_success_default_code=1000 Index: src/jorphan/org/apache/jorphan/reflect/ClassFinder.java =================================================================== --- src/jorphan/org/apache/jorphan/reflect/ClassFinder.java (revision 812523) +++ src/jorphan/org/apache/jorphan/reflect/ClassFinder.java (working copy) @@ -21,6 +21,8 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Enumeration; @@ -108,7 +110,53 @@ return false; } } + + private static class AnnoFilterTreeSet extends TreeSet{ + private final boolean inner; // are inner classes OK? + // hack to reduce the need to load every class in non-GUI mode, which only needs functions + // TODO perhaps use BCEL to scan class files instead? + private final String contains; // class name should contain this string + private final String notContains; // class name should not contain this string + private final Class[] annotations; // parent classes to check + private final transient ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); // Potentially expensive; do it once + AnnoFilterTreeSet(Class []annotations, boolean inner, String contains, String notContains){ + super(); + this.annotations = annotations; + this.inner=inner; + this.contains=contains; + this.notContains=notContains; + } + /** + * Override the superclass so we only add classnames that + * meet the criteria. + * + * @param s - classname (must be a String) + * @return true if it is a new entry + * + * @see java.util.TreeSet#add(java.lang.Object) + */ + public boolean add(String s){ + log.debug("adding in AnnoFilterTreeSet"); + if (contains(s)) { + return false;// No need to check it again + } + if (contains!=null && s.indexOf(contains) == -1){ + return false; // It does not contain a required string + } + if (notContains!=null && s.indexOf(notContains) != -1){ + return false; // It contains a banned string + } + if ((s.indexOf("$") == -1) || inner) { // $NON-NLS-1$ + if (hasAnnotationOnMethod(annotations,s, contextClassLoader)) { + System.out.println("adding "+s); + return super.add(s); + } + } + return false; + } + } + /** * Convenience method for * findClassesThatExtend(Class[], boolean) @@ -221,7 +269,60 @@ return new ArrayList(listClasses);//subClassList); } + + public static List findClassesWithMethodAnnotation(String[] strPathsOrJars, + final Class[] annotations, final boolean innerClasses, + String contains, String notContains) + throws IOException { + if (log.isDebugEnabled()) { + for (int i = 0; i < annotations.length ; i++){ + log.debug("superclass: "+annotations[i].getName()); + } + } + + // Find all jars in the search path + strPathsOrJars = addJarsInPath(strPathsOrJars); + for (int k = 0; k < strPathsOrJars.length; k++) { + strPathsOrJars[k] = fixPathEntry(strPathsOrJars[k]); + if (log.isDebugEnabled()) { + log.debug("strPathsOrJars : " + strPathsOrJars[k]); + } + } + + // Now eliminate any classpath entries that do not "match" the search + List listPaths = getClasspathMatches(strPathsOrJars); + if (log.isDebugEnabled()) { + Iterator tIter = listPaths.iterator(); + while (tIter.hasNext()) { + log.debug("listPaths : " + tIter.next()); + } + } + + Set listClasses = new AnnoFilterTreeSet(annotations, innerClasses, contains, notContains); + // first get all the classes + findClassesInPaths(listPaths, listClasses); + if (log.isDebugEnabled()) { + log.debug("listClasses.size()="+listClasses.size()); + Iterator tIter = listClasses.iterator(); + while (tIter.hasNext()) { + log.debug("listClasses : " + tIter.next()); + } + } + +// // Now keep only the required classes +// Set subClassList = findAllSubclasses(superClasses, listClasses, innerClasses); +// if (log.isDebugEnabled()) { +// log.debug("subClassList.size()="+subClassList.size()); +// Iterator tIter = subClassList.iterator(); +// while (tIter.hasNext()) { +// log.debug("subClassList : " + tIter.next()); +// } +// } + + return new ArrayList(listClasses);//subClassList); +} + /* * Returns the classpath entries that match the search list of jars and paths */ @@ -389,6 +490,29 @@ } return false; } + + private static boolean hasAnnotationOnMethod(Class[] annotations, String classInQuestion, + ClassLoader contextClassLoader ){ + try{ + log.info("testing "+classInQuestion+" for annotations:"); + for(Class c : annotations) { + log.info(""+c); + } + Class c = Class.forName(classInQuestion, false, contextClassLoader); + for(Method method : c.getMethods()) { + for(int i = 0;i annotation = annotations[i]; + if(method.isAnnotationPresent(annotation)) { + log.debug("found annotation on "+classInQuestion); + return true; + } + } + } + } catch (Throwable ignored) { + log.debug(ignored.getLocalizedMessage()); + } + return false; + } /* Index: src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitAnnotatedTestSamplerGui.java =================================================================== --- src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitAnnotatedTestSamplerGui.java (revision 0) +++ src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitAnnotatedTestSamplerGui.java (revision 0) @@ -0,0 +1,89 @@ +/* + * 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.jmeter.protocol.java.control.gui; + +import java.awt.event.ActionListener; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.event.ChangeListener; + +import org.apache.jmeter.protocol.java.sampler.JUnitAnnotatedSampler; +import org.apache.jmeter.protocol.java.sampler.JUnitSampler; +import org.apache.jmeter.testelement.TestElement; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.jorphan.reflect.ClassFinder; +import org.apache.log.Logger; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * The JUnitAnnotatedTestSamplerGui class provides the user interface + * for the {@link JUnitAnnotatedSampler}. + * + */ +public class JUnitAnnotatedTestSamplerGui extends JUnitTestSamplerGui +implements ChangeListener, ActionListener +{ + private static final Logger log = LoggingManager.getLoggerForClass(); + + public JUnitAnnotatedTestSamplerGui() { + super(); + super.init(); + } + + public String getLabelResource() + { + return "junit_4_request"; //$NON-NLS-1$ + } + + public TestElement createTestElement() + { + JUnitSampler sampler = new JUnitAnnotatedSampler(); + modifyTestElement(sampler); + return sampler; + } + + List findTestableClasses() throws IOException { + return ClassFinder.findClassesWithMethodAnnotation(SPATHS, + new Class[]{Test.class}, false, null, null); + } + + public Method[] getMethods(Object obj) + { + Method[] meths = obj.getClass().getMethods(); + List list = new ArrayList(); + for (int idx=0; idx < meths.length; idx++){ + if (meths[idx].isAnnotationPresent(Test.class) || + meths[idx].isAnnotationPresent(BeforeClass.class) || + meths[idx].isAnnotationPresent(AfterClass.class)) { + list.add(meths[idx]); + } + } + if (list.size() > 0){ + Method[] rmeth = new Method[list.size()]; + return list.toArray(rmeth); + } + return new Method[0]; + } +} + Index: src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java =================================================================== --- src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java (revision 812523) +++ src/junit/org/apache/jmeter/protocol/java/control/gui/JUnitTestSamplerGui.java (working copy) @@ -68,7 +68,7 @@ private static final String ONETIMETEARDOWN = "oneTimeTearDown"; //$NON-NLS-1$ private static final String SUITE = "suite"; //$NON-NLS-1$ - private static final String[] SPATHS; + static final String[] SPATHS; static { String paths[]; @@ -131,7 +131,7 @@ /** A combo box allowing the user to choose a test class. */ private JComboBox classnameCombo; private JComboBox methodName; - private transient TestCase TESTCLASS = null; + private transient Object TESTCLASS = null; private List METHODLIST = null; private transient ClassFilter FILTER = new ClassFilter(); @@ -154,7 +154,7 @@ /** * Initialize the GUI components and layout. */ - private void init() + void init() { setLayout(new BorderLayout(0, 5)); setBorder(makeBorder()); @@ -172,10 +172,8 @@ try { // Find all the classes which extend junit.framework.TestCase - CLASSLIST = - ClassFinder.findClassesThatExtend( - SPATHS, - new Class[] { TestCase.class }); + CLASSLIST = findTestableClasses(); + } catch (IOException e) { @@ -328,7 +326,7 @@ String className = ((String) classnameCombo.getSelectedItem()); if (className != null) { - TESTCLASS = (TestCase)JUnitSampler.getClassInstance(className, + TESTCLASS = JUnitSampler.getClassInstance(className, constructorLabel.getText()); if (TESTCLASS == null) { clearMethodCombo(); @@ -426,5 +424,11 @@ } } } + + List findTestableClasses() throws IOException { + return ClassFinder.findClassesThatExtend( + SPATHS, + new Class[] { TestCase.class }); + } } Index: src/junit/org/apache/jmeter/protocol/java/sampler/JUnitAnnotatedSampler.java =================================================================== --- src/junit/org/apache/jmeter/protocol/java/sampler/JUnitAnnotatedSampler.java (revision 0) +++ src/junit/org/apache/jmeter/protocol/java/sampler/JUnitAnnotatedSampler.java (revision 0) @@ -0,0 +1,275 @@ +/* + * 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.jmeter.protocol.java.sampler; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Enumeration; + +import junit.framework.AssertionFailedError; +import junit.framework.ComparisonFailure; +import junit.framework.TestCase; +import junit.framework.TestFailure; +import junit.framework.TestResult; + +import org.apache.jmeter.samplers.Entry; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jorphan.logging.LoggingManager; +import org.apache.log.Logger; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.Test.None; + +/** + * Sampler class for annotated junit test cases. This + * leverages the {@link JUnitSampler} code pretty heavily. + * The difference is that this class will look for the @Test + * annotation instead of extensions of TestCase. + */ + +public class JUnitAnnotatedSampler extends JUnitSampler +{ + private transient Object TEST_INSTANCE = null; + private static final Logger log = LoggingManager.getLoggerForClass(); + private static final long serialVersionUID = 1L; // Remember to change this when the class changes ... + + /* (non-Javadoc) + * @see org.apache.jmeter.samplers.Sampler#sample(org.apache.jmeter.samplers.Entry) + */ + public SampleResult sample(Entry entry) { + SampleResult sresult = new SampleResult(); + String rlabel = getConstructorString(); + if (rlabel.length()== 0) { + rlabel = JUnitSampler.class.getName(); + } + sresult.setSampleLabel(getName());// Bug 41522 - don't use rlabel here + final String methodName = getMethod(); + final String className = getClassname(); + sresult.setSamplerData(className + "." + methodName); + // check to see if the test class is null. if it is, we create + // a new instance. this should only happen at the start of a + // test run + if (this.TEST_INSTANCE == null) { + this.TEST_INSTANCE = getClassInstance(className,rlabel); + } + if (this.TEST_INSTANCE != null){ + initMethodObjects(this.TEST_INSTANCE); + // create a new TestResult + TestResult tr = new TestResult(); +// this.TEST_INSTANCE.setName(methodName); + TestCase tc = null; + try { + tc = new AnnotatedTestCase(); + } catch (SecurityException e) { + log.error(e.getLocalizedMessage()); + return sresult; + } catch( NoSuchMethodException e) { + log.error(e.getLocalizedMessage()); + return sresult; + } + tc.setName(methodName); + try { + + if (!getDoNotSetUpTearDown() && SETUP_METHOD != null){ + try { + SETUP_METHOD.invoke(this.TEST_INSTANCE,new Object[0]); + } catch (InvocationTargetException e) { + tr.addFailure(tc, + new AssertionFailedError(e.getMessage())); + } catch (IllegalAccessException e) { + tr.addFailure(tc, + new AssertionFailedError(e.getMessage())); + } catch (IllegalArgumentException e) { + tr.addFailure(tc, + new AssertionFailedError(e.getMessage())); + } + } + final Method m = getMethod(this.TEST_INSTANCE,methodName); + long timeout = 0L; + Class expectedException = null; + Test annotation = m.getAnnotation(Test.class); + if(null != annotation) { + expectedException = annotation.expected(); + timeout = annotation.timeout(); + } + + tr.startTest(tc); + sresult.sampleStart(); + // Do not use TestCase.run(TestResult) method, since it will + // call setUp and tearDown. Doing that will result in calling + // the setUp and tearDown method twice and the elapsed time + // will include setup and teardown. + Throwable thrown = null; + try { + tc.runBare(); + } catch(Throwable t) { + thrown = t; + } + tr.endTest(tc); + sresult.sampleEnd(); + + if(null == thrown) { + if (expectedException != None.class) { + tr.addFailure( + tc, + new AssertionFailedError( + "No error was generated for a test case which specifies an error.")); + throw new Exception( + "Unexpected lack of exception from the test case: " + + getMethod()); + } + else { + //this is a pass condition + } + } + else { + //unravel 1x. InvocationTargetException wraps the real exception. + thrown = thrown.getCause(); + if (expectedException.isAssignableFrom(thrown.getClass())) +// && thrown.getClass().isAssignableFrom(expectedException)) + { + //The test actually passed + log.debug("Test passed for " + getMethod()); + } else { + tr.addFailure(tc, new AssertionFailedError("The wrong exception was thrown from the test case")); + throw new Exception("The wrong exception was thrown from the test case"); + } + } + + if( timeout > 0L && timeout < sresult.getTime()) { + tr.addFailure(tc, new AssertionFailedError("Test took longer than speficied timeout.")); + } + + if (!getDoNotSetUpTearDown() && TDOWN_METHOD != null){ + TDOWN_METHOD.invoke(TEST_INSTANCE,new Object[0]); + } + + } catch (InvocationTargetException e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } catch (IllegalAccessException e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } catch (ComparisonFailure e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } catch (IllegalArgumentException e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } catch (Exception e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } catch (Throwable e) { + sresult.setResponseCode(getErrorCode()); + sresult.setResponseMessage(getError()); + sresult.setResponseData(e.getMessage().getBytes()); + sresult.setSuccessful(false); + } + if ( !tr.wasSuccessful() ){ + sresult.setSuccessful(false); + StringBuffer buf = new StringBuffer(); + buf.append( getFailure() ); + Enumeration en = tr.errors(); + while (en.hasMoreElements()){ + Object item = en.nextElement(); + if (getAppendError() && item instanceof TestFailure) { + buf.append( "Trace -- "); + buf.append( ((TestFailure)item).trace() ); + buf.append( "Failure -- "); + buf.append( ((TestFailure)item).toString() ); + } else if (getAppendException() && item instanceof Throwable) { + buf.append( ((Throwable)item).getMessage() ); + } + } + sresult.setResponseMessage(buf.toString()); + sresult.setRequestHeaders(buf.toString()); + sresult.setResponseCode(getFailureCode()); + } else { + // this means there's no failures + sresult.setSuccessful(true); + sresult.setResponseMessage(getSuccess()); + sresult.setResponseCode(getSuccessCode()); + sresult.setResponseData("Not Applicable".getBytes()); + } + } else { + // we should log a warning, but allow the test to keep running + sresult.setSuccessful(false); + // this should be externalized to the properties + sresult.setResponseMessage("failed to create an instance of the class"); + } + sresult.setBytes(0); + sresult.setContentType("text"); + sresult.setDataType("Not Applicable"); + sresult.setRequestHeaders("Not Applicable"); + return sresult; + } + + /** + * Method tries to get the setUp and tearDown method for the class + * @param tc + */ + private void initMethodObjects(Object testCase){ + if (!super.checkStartUpTearDown && !getDoNotSetUpTearDown()) { + if (super.SETUP_METHOD == null) { + super.SETUP_METHOD = getMethodWithAnnotation(testCase, Before.class); + } + if (super.TDOWN_METHOD == null) { + super.TDOWN_METHOD = getMethodWithAnnotation(testCase, After.class); + } + super.checkStartUpTearDown = true; + } + } + + public Method getMethodWithAnnotation(Object clazz, Class annotation) { + if(null != clazz && null != annotation) { + for(Method m : clazz.getClass().getMethods()) { + if(m.isAnnotationPresent(annotation)) { + return m; + } + } + } + return null; + } + + class AnnotatedTestCase extends TestCase { + Method m = null; + public AnnotatedTestCase() throws SecurityException, NoSuchMethodException { + m = TEST_INSTANCE.getClass().getMethod(getMethod(), (Class[])null); + } + + protected void runTest() throws Throwable { +// for(Method m :TEST_INSTANCE.getClass().getMethods()) { +// log.debug("available method: "+m); +// } + m.invoke(TEST_INSTANCE, (Object[])null); + } + } +} Index: src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java =================================================================== --- src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java (revision 812523) +++ src/junit/org/apache/jmeter/protocol/java/sampler/JUnitSampler.java (working copy) @@ -72,9 +72,9 @@ public static final String RUNTEST = "run"; /// the Method objects for setUp and tearDown methods - private transient Method SETUP_METHOD = null; - private transient Method TDOWN_METHOD = null; - private boolean checkStartUpTearDown = false; + protected transient Method SETUP_METHOD = null; + protected transient Method TDOWN_METHOD = null; + protected boolean checkStartUpTearDown = false; private transient TestCase TEST_INSTANCE = null;