1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 package org.apache.jmeter.protocol.java.sampler; 19 20 import java.lang.reflect.Constructor; 21 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.lang.reflect.Modifier; 24 import java.util.Enumeration; 25 26 import junit.framework.AssertionFailedError; 27 import junit.framework.ComparisonFailure; 28 import junit.framework.Protectable; 29 import junit.framework.TestCase; 30 import junit.framework.TestFailure; 31 import junit.framework.TestResult; 32 33 import org.apache.jmeter.samplers.AbstractSampler; 34 import org.apache.jmeter.samplers.Entry; 35 import org.apache.jmeter.samplers.SampleResult; 36 import org.apache.jorphan.logging.LoggingManager; 37 import org.apache.log.Logger; 38 39 /** 40 * 41 * This is a basic implementation that runs a single test method of 42 * a JUnit test case. The current implementation will use the string 43 * constructor first. If the test class does not declare a string 44 * constructor, the sampler will try empty constructor. 45 */ 46 public class JUnitSampler extends AbstractSampler { 47 48 private static final Logger log = LoggingManager.getLoggerForClass(); 49 50 private static final long serialVersionUID = 2331L; // Remember to change this when the class changes ... 51 52 /** 53 * Property key representing the classname of the JavaSamplerClient to 54 * user. 55 */ 56 public static final String CLASSNAME = "junitSampler.classname"; 57 public static final String CONSTRUCTORSTRING = "junitsampler.constructorstring"; 58 public static final String METHOD = "junitsampler.method"; 59 public static final String ERROR = "junitsampler.error"; 60 public static final String ERRORCODE = "junitsampler.error.code"; 61 public static final String FAILURE = "junitsampler.failure"; 62 public static final String FAILURECODE = "junitsampler.failure.code"; 63 public static final String SUCCESS = "junitsampler.success"; 64 public static final String SUCCESSCODE = "junitsampler.success.code"; 65 public static final String FILTER = "junitsampler.pkg.filter"; 66 public static final String DOSETUP = "junitsampler.exec.setup"; 67 public static final String APPEND_ERROR = "junitsampler.append.error"; 68 public static final String APPEND_EXCEPTION = "junitsampler.append.exception"; 69 70 public static final String SETUP = "setUp"; 71 public static final String TEARDOWN = "tearDown"; 72 public static final String RUNTEST = "run"; 73 74 /// the Method objects for setUp and tearDown methods 75 private transient Method SETUP_METHOD = null; 76 private transient Method TDOWN_METHOD = null; 77 private boolean checkStartUpTearDown = false; 78 79 private transient TestCase TEST_INSTANCE = null; 80 81 public JUnitSampler(){ 82 } 83 84 /** 85 * Method tries to get the setUp and tearDown method for the class 86 * @param tc 87 */ 88 private void initMethodObjects(TestCase tc){ 89 if (!this.checkStartUpTearDown && !getDoNotSetUpTearDown()) { 90 if (SETUP_METHOD == null) { 91 SETUP_METHOD = getMethod(tc, SETUP); 92 } 93 if (TDOWN_METHOD == null) { 94 TDOWN_METHOD = getMethod(tc, TEARDOWN); 95 } 96 this.checkStartUpTearDown = true; 97 } 98 } 99 100 /** 101 * Sets the Classname attribute of the JavaConfig object 102 * 103 * @param classname 104 * the new Classname value 105 */ 106 public void setClassname(String classname) 107 { 108 setProperty(CLASSNAME, classname); 109 } 110 111 /** 112 * Gets the Classname attribute of the JavaConfig object 113 * 114 * @return the Classname value 115 */ 116 public String getClassname() 117 { 118 return getPropertyAsString(CLASSNAME); 119 } 120 121 /** 122 * Set the string label used to create an instance of the 123 * test with the string constructor. 124 * @param constr 125 */ 126 public void setConstructorString(String constr) 127 { 128 setProperty(CONSTRUCTORSTRING,constr); 129 } 130 131 /** 132 * get the string passed to the string constructor 133 */ 134 public String getConstructorString() 135 { 136 return getPropertyAsString(CONSTRUCTORSTRING); 137 } 138 139 /** 140 * Return the name of the method to test 141 */ 142 public String getMethod(){ 143 return getPropertyAsString(METHOD); 144 } 145 146 /** 147 * Method should add the JUnit testXXX method to the list at 148 * the end, since the sequence matters. 149 * @param methodName 150 */ 151 public void setMethod(String methodName){ 152 setProperty(METHOD,methodName); 153 } 154 155 /** 156 * get the success message 157 */ 158 public String getSuccess(){ 159 return getPropertyAsString(SUCCESS); 160 } 161 162 /** 163 * set the success message 164 * @param success 165 */ 166 public void setSuccess(String success){ 167 setProperty(SUCCESS,success); 168 } 169 170 /** 171 * get the success code defined by the user 172 */ 173 public String getSuccessCode(){ 174 return getPropertyAsString(SUCCESSCODE); 175 } 176 177 /** 178 * set the succes code. the success code should 179 * be unique. 180 * @param code 181 */ 182 public void setSuccessCode(String code){ 183 setProperty(SUCCESSCODE,code); 184 } 185 186 /** 187 * get the failure message 188 */ 189 public String getFailure(){ 190 return getPropertyAsString(FAILURE); 191 } 192 193 /** 194 * set the failure message 195 * @param fail 196 */ 197 public void setFailure(String fail){ 198 setProperty(FAILURE,fail); 199 } 200 201 /** 202 * The failure code is used by other components 203 */ 204 public String getFailureCode(){ 205 return getPropertyAsString(FAILURECODE); 206 } 207 208 /** 209 * Provide some unique code to denote a type of failure 210 * @param code 211 */ 212 public void setFailureCode(String code){ 213 setProperty(FAILURECODE,code); 214 } 215 216 /** 217 * return the descriptive error for the test 218 */ 219 public String getError(){ 220 return getPropertyAsString(ERROR); 221 } 222 223 /** 224 * provide a descriptive error for the test method. For 225 * a description of the difference between failure and 226 * error, please refer to the following url 227 * http://junit.sourceforge.net/doc/faq/faq.htm#tests_9 228 * @param error 229 */ 230 public void setError(String error){ 231 setProperty(ERROR,error); 232 } 233 234 /** 235 * return the error code for the test method. it should 236 * be an unique error code. 237 */ 238 public String getErrorCode(){ 239 return getPropertyAsString(ERRORCODE); 240 } 241 242 /** 243 * provide an unique error code for when the test 244 * does not pass the assert test. 245 * @param code 246 */ 247 public void setErrorCode(String code){ 248 setProperty(ERRORCODE,code); 249 } 250 251 /** 252 * return the comma separated string for the filter 253 */ 254 public String getFilterString(){ 255 return getPropertyAsString(FILTER); 256 } 257 258 /** 259 * set the filter string in comman separated format 260 * @param text 261 */ 262 public void setFilterString(String text){ 263 setProperty(FILTER,text); 264 } 265 266 /** 267 * if the sample shouldn't call setup/teardown, the 268 * method returns true. It's meant for onetimesetup 269 * and onetimeteardown. 270 */ 271 public boolean getDoNotSetUpTearDown(){ 272 return getPropertyAsBoolean(DOSETUP); 273 } 274 275 /** 276 * set the setup/teardown option 277 * @param setup 278 */ 279 public void setDoNotSetUpTearDown(boolean setup){ 280 setProperty(DOSETUP,String.valueOf(setup)); 281 } 282 283 /** 284 * If append error is not set, by default it is set to false, 285 * which means users have to explicitly set the sampler to 286 * append the assert errors. Because of how junit works, there 287 * should only be one error 288 */ 289 public boolean getAppendError() { 290 return getPropertyAsBoolean(APPEND_ERROR,false); 291 } 292 293 public void setAppendError(boolean error) { 294 setProperty(APPEND_ERROR,String.valueOf(error)); 295 } 296 297 /** 298 * If append exception is not set, by default it is set to false. 299 * Users have to explicitly set it to true to see the exceptions 300 * in the result tree. 301 */ 302 public boolean getAppendException() { 303 return getPropertyAsBoolean(APPEND_EXCEPTION,false); 304 } 305 306 public void setAppendException(boolean exc) { 307 setProperty(APPEND_EXCEPTION,String.valueOf(exc)); 308 } 309 310 /* (non-Javadoc) 311 * @see org.apache.jmeter.samplers.Sampler#sample(org.apache.jmeter.samplers.Entry) 312 */ 313 public SampleResult sample(Entry entry) { 314 SampleResult sresult = new SampleResult(); 315 String rlabel = getConstructorString(); 316 if (rlabel.length()== 0) { 317 rlabel = JUnitSampler.class.getName(); 318 } 319 sresult.setSampleLabel(getName());// Bug 41522 - don't use rlabel here 320 final String methodName = getMethod(); 321 final String className = getClassname(); 322 sresult.setSamplerData(className + "." + methodName); 323 // check to see if the test class is null. if it is, we create 324 // a new instance. this should only happen at the start of a 325 // test run 326 if (this.TEST_INSTANCE == null) { 327 this.TEST_INSTANCE = (TestCase)getClassInstance(className,rlabel); 328 } 329 if (this.TEST_INSTANCE != null){ 330 initMethodObjects(this.TEST_INSTANCE); 331 // create a new TestResult 332 TestResult tr = new TestResult(); 333 this.TEST_INSTANCE.setName(methodName); 334 try { 335 336 if (!getDoNotSetUpTearDown() && SETUP_METHOD != null){ 337 try { 338 SETUP_METHOD.invoke(this.TEST_INSTANCE,new Class[0]); 339 } catch (InvocationTargetException e) { 340 tr.addFailure(this.TEST_INSTANCE, 341 new AssertionFailedError(e.getMessage())); 342 } catch (IllegalAccessException e) { 343 tr.addFailure(this.TEST_INSTANCE, 344 new AssertionFailedError(e.getMessage())); 345 } catch (IllegalArgumentException e) { 346 tr.addFailure(this.TEST_INSTANCE, 347 new AssertionFailedError(e.getMessage())); 348 } 349 } 350 final Method m = getMethod(this.TEST_INSTANCE,methodName); 351 final TestCase theClazz = this.TEST_INSTANCE; 352 tr.startTest(this.TEST_INSTANCE); 353 sresult.sampleStart(); 354 // Do not use TestCase.run(TestResult) method, since it will 355 // call setUp and tearDown. Doing that will result in calling 356 // the setUp and tearDown method twice and the elapsed time 357 // will include setup and teardown. 358 Protectable p = new Protectable() { 359 public void protect() throws Throwable { 360 m.invoke(theClazz,new Class[0]); 361 } 362 }; 363 tr.runProtected(theClazz, p); 364 tr.endTest(this.TEST_INSTANCE); 365 sresult.sampleEnd(); 366 367 if (!getDoNotSetUpTearDown() && TDOWN_METHOD != null){ 368 TDOWN_METHOD.invoke(TEST_INSTANCE,new Class[0]); 369 } 370 } catch (InvocationTargetException e) { 371 // log.warn(e.getMessage()); 372 sresult.setResponseCode(getErrorCode()); 373 sresult.setResponseMessage(getError()); 374 sresult.setResponseData(e.getMessage().getBytes()); 375 sresult.setSuccessful(false); 376 } catch (IllegalAccessException e) { 377 // log.warn(e.getMessage()); 378 sresult.setResponseCode(getErrorCode()); 379 sresult.setResponseMessage(getError()); 380 sresult.setResponseData(e.getMessage().getBytes()); 381 sresult.setSuccessful(false); 382 } catch (ComparisonFailure e) { 383 sresult.setResponseCode(getErrorCode()); 384 sresult.setResponseMessage(getError()); 385 sresult.setResponseData(e.getMessage().getBytes()); 386 sresult.setSuccessful(false); 387 } catch (IllegalArgumentException e) { 388 sresult.setResponseCode(getErrorCode()); 389 sresult.setResponseMessage(getError()); 390 sresult.setResponseData(e.getMessage().getBytes()); 391 sresult.setSuccessful(false); 392 } catch (Exception e) { 393 sresult.setResponseCode(getErrorCode()); 394 sresult.setResponseMessage(getError()); 395 sresult.setResponseData(e.getMessage().getBytes()); 396 sresult.setSuccessful(false); 397 } catch (Throwable e) { 398 sresult.setResponseCode(getErrorCode()); 399 sresult.setResponseMessage(getError()); 400 sresult.setResponseData(e.getMessage().getBytes()); 401 sresult.setSuccessful(false); 402 } 403 if ( !tr.wasSuccessful() ){ 404 sresult.setSuccessful(false); 405 StringBuffer buf = new StringBuffer(); 406 buf.append( getFailure() ); 407 Enumeration en = tr.errors(); 408 while (en.hasMoreElements()){ 409 Object item = en.nextElement(); 410 if (getAppendError() && item instanceof TestFailure) { 411 buf.append( "Trace -- "); 412 buf.append( ((TestFailure)item).trace() ); 413 buf.append( "Failure -- "); 414 buf.append( ((TestFailure)item).toString() ); 415 } else if (getAppendException() && item instanceof Throwable) { 416 buf.append( ((Throwable)item).getMessage() ); 417 } 418 } 419 sresult.setResponseMessage(buf.toString()); 420 sresult.setRequestHeaders(buf.toString()); 421 sresult.setResponseCode(getFailureCode()); 422 } else { 423 // this means there's no failures 424 sresult.setSuccessful(true); 425 sresult.setResponseMessage(getSuccess()); 426 sresult.setResponseCode(getSuccessCode()); 427 sresult.setResponseData("Not Applicable".getBytes()); 428 } 429 } else { 430 // we should log a warning, but allow the test to keep running 431 sresult.setSuccessful(false); 432 // this should be externalized to the properties 433 sresult.setResponseMessage("failed to create an instance of the class"); 434 } 435 sresult.setBytes(0); 436 sresult.setContentType("text"); 437 sresult.setDataType("Not Applicable"); 438 sresult.setRequestHeaders("Not Applicable"); 439 return sresult; 440 } 441 442 /** 443 * If the method is not able to create a new instance of the 444 * class, it returns null and logs all the exceptions at 445 * warning level. 446 */ 447 public static Object getClassInstance(String className, String label){ 448 Object testclass = null; 449 if (className != null){ 450 Constructor con = null; 451 Constructor strCon = null; 452 Class theclazz = null; 453 Object[] strParams = null; 454 Object[] params = null; 455 try 456 { 457 theclazz = 458 Thread.currentThread().getContextClassLoader().loadClass(className.trim()); 459 } catch (ClassNotFoundException e) { 460 log.warn("ClassNotFoundException:: " + e.getMessage()); 461 } 462 if (theclazz != null) { 463 // first we see if the class declares a string 464 // constructor. if it is doesn't we look for 465 // empty constructor. 466 try { 467 strCon = theclazz.getDeclaredConstructor( 468 new Class[] {String.class}); 469 // we have to check and make sure the constructor is 470 // accessible. if we didn't it would throw an exception 471 // and cause a NPE. 472 if (label == null || label.length() == 0) { 473 label = className; 474 } 475 if (strCon.getModifiers() == Modifier.PUBLIC) { 476 strParams = new Object[]{label}; 477 } else { 478 strCon = null; 479 } 480 } catch (NoSuchMethodException e) { 481 log.info("String constructor:: " + e.getMessage()); 482 } 483 try { 484 con = theclazz.getDeclaredConstructor(new Class[0]); 485 if (con != null){ 486 params = new Object[]{}; 487 } 488 } catch (NoSuchMethodException e) { 489 log.info("Empty constructor:: " + e.getMessage()); 490 } 491 try { 492 // if the string constructor is not null, we use it. 493 // if the string constructor is null, we use the empty 494 // constructor to get a new instance 495 if (strCon != null) { 496 testclass = strCon.newInstance(strParams); 497 } else if (con != null){ 498 testclass = con.newInstance(params); 499 } 500 } catch (InvocationTargetException e) { 501 log.warn(e.getMessage()); 502 } catch (InstantiationException e) { 503 log.info(e.getMessage()); 504 } catch (IllegalAccessException e) { 505 log.info(e.getMessage()); 506 } 507 } 508 } 509 return testclass; 510 } 511 512 /** 513 * 514 * @param clazz 515 * @param method 516 * @return the method or null if an error occurred 517 */ 518 public Method getMethod(Object clazz, String method){ 519 if (clazz != null && method != null){ 520 // log.info("class " + clazz.getClass().getName() + 521 // " method name is " + method); 522 try { 523 return clazz.getClass().getMethod(method,new Class[0]); 524 } catch (NoSuchMethodException e) { 525 log.warn(e.getMessage()); 526 } 527 } 528 return null; 529 } 530 531 public Method getRunTestMethod(Object clazz){ 532 if (clazz != null){ 533 // log.info("class " + clazz.getClass().getName() + 534 // " method name is " + RUNTEST); 535 try { 536 Class[] param = {TestResult.class}; 537 return clazz.getClass().getMethod(RUNTEST,param); 538 } catch (NoSuchMethodException e) { 539 log.warn(e.getMessage()); 540 } 541 } 542 return null; 543 } 544 }