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