001: /*
002: * ========================================================================
003: *
004: * Copyright 2001-2004 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * ========================================================================
019: */
020: package org.apache.cactus.internal.client;
021:
022: import java.lang.reflect.InvocationTargetException;
023: import java.lang.reflect.Method;
024:
025: import junit.framework.Assert;
026: import junit.framework.Test;
027:
028: import org.apache.cactus.Request;
029: import org.apache.cactus.internal.util.JUnitVersionHelper;
030: import org.apache.cactus.internal.util.TestCaseImplementChecker;
031: import org.apache.cactus.spi.client.ResponseObjectFactory;
032: import org.apache.cactus.spi.client.connector.ProtocolHandler;
033: import org.apache.cactus.spi.client.connector.ProtocolState;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036:
037: /**
038: * Provides the ability to run common code before and after each test on the
039: * client side. All the methods provided are independent of any communication
040: * protocol between client side and server side (HTTP, JMS, etc). Any protocol
041: * dependent methods must be provided and implemented in the
042: * {@link ProtocolHandler} implementation class.
043: *
044: * @version $Id: ClientTestCaseCaller.java 238991 2004-05-22 11:34:50Z vmassol $
045: */
046: public class ClientTestCaseCaller extends Assert {
047: /**
048: * The prefix of a test method.
049: */
050: protected static final String TEST_METHOD_PREFIX = "test";
051:
052: /**
053: * The prefix of a begin test method.
054: */
055: protected static final String BEGIN_METHOD_PREFIX = "begin";
056:
057: /**
058: * The prefix of an end test method.
059: */
060: protected static final String END_METHOD_PREFIX = "end";
061:
062: /**
063: * The name of the method that is called before each test on the client
064: * side (if it exists).
065: */
066: protected static final String CLIENT_GLOBAL_BEGIN_METHOD = "begin";
067:
068: /**
069: * The name of the method that is called after each test on the client
070: * side (if it exists).
071: */
072: protected static final String CLIENT_GLOBAL_END_METHOD = "end";
073:
074: /**
075: * The logger (only used on the client side).
076: */
077: private Log logger;
078:
079: /**
080: * Pure JUnit Test Case that we are wrapping (if any)
081: */
082: private Test wrappedTest;
083:
084: /**
085: * The test we are delegating for.
086: */
087: private Test delegatedTest;
088:
089: /**
090: * The protocol handler to use to execute the tests on the server side.
091: */
092: private ProtocolHandler protocolHandler;
093:
094: // Constructors ---------------------------------------------------------
095:
096: /**
097: * @param theDelegatedTest the test we are delegating for
098: * @param theWrappedTest the test being wrapped by this delegate (or null
099: * if none)
100: * @param theProtocolHandler the protocol handler to use to execute the
101: * tests on the server side
102: */
103: public ClientTestCaseCaller(Test theDelegatedTest,
104: Test theWrappedTest, ProtocolHandler theProtocolHandler) {
105: if (theDelegatedTest == null) {
106: throw new IllegalStateException(
107: "The test object passed must not be null");
108: }
109:
110: setDelegatedTest(theDelegatedTest);
111: setWrappedTest(theWrappedTest);
112: this .protocolHandler = theProtocolHandler;
113: }
114:
115: // Public methods -------------------------------------------------------
116:
117: /**
118: * Execute begin and end methods and calls the different
119: * {@link ProtocolHandler} lifecycle methods to execute the test
120: * on the server side.
121: *
122: * Note that this method is overriden from the JUnit
123: * {@link junit.framework.TestCase} class in order to prevent JUnit from
124: * calling the {@link junit.framework.TestCase#setUp()} and
125: * {@link junit.framework.TestCase#tearDown()} methods on the client side.
126: * instead we are calling the server redirector proxy and the setup and
127: * teardown methods will be executed on the server side.
128: *
129: * @exception Throwable if any error happens during the execution of
130: * the test
131: */
132: public void runTest() throws Throwable {
133: Request request = this .protocolHandler.createRequest();
134:
135: // Call the set up and begin methods to fill the request object
136: callGlobalBeginMethod(request);
137: callBeginMethod(request);
138:
139: // Run the server test
140: ProtocolState state = this .protocolHandler.runTest(
141: getDelegatedTest(), getWrappedTest(), request);
142:
143: // Call the end method
144: Object response = callEndMethod(request, this .protocolHandler
145: .createResponseObjectFactory(state));
146:
147: // call the tear down method
148: callGlobalEndMethod(request, this .protocolHandler
149: .createResponseObjectFactory(state), response);
150:
151: this .protocolHandler.afterTest(state);
152: }
153:
154: /**
155: * @return The logger used by the <code>TestCase</code> class and
156: * subclasses to perform logging.
157: */
158: public final Log getLogger() {
159: return this .logger;
160: }
161:
162: /**
163: * Perform client side initializations before each test, such as
164: * re-initializating the logger and printing some logging information.
165: */
166: public void runBareInit() {
167: // We make sure we reinitialize The logger with the name of the
168: // current extending class so that log statements will contain the
169: // actual class name (that's why the logged instance is not static).
170: this .logger = LogFactory.getLog(this .getClass());
171:
172: // Mark beginning of test on client side
173: getLogger().debug(
174: "------------- Test: " + this .getCurrentTestName());
175: }
176:
177: /**
178: * Call the test case begin method.
179: *
180: * @param theRequest the request object to pass to the begin method.
181: * @exception Throwable any error that occurred when calling the begin
182: * method for the current test case.
183: */
184: public void callBeginMethod(Request theRequest) throws Throwable {
185: callGenericBeginMethod(theRequest, getBeginMethodName());
186: }
187:
188: /**
189: * Call the test case end method
190: *
191: * @param theRequest the request data that were used to open the
192: * connection.
193: * @param theResponseFactory the factory to use to return response objects.
194: * @return the created Reponse object
195: * @exception Throwable any error that occurred when calling the end method
196: * for the current test case.
197: */
198: public Object callEndMethod(Request theRequest,
199: ResponseObjectFactory theResponseFactory) throws Throwable {
200: return callGenericEndMethod(theRequest, theResponseFactory,
201: getEndMethodName(), null);
202: }
203:
204: /**
205: * Call the global begin method. This is the method that is called before
206: * each test if it exists. It is called on the client side only.
207: *
208: * @param theRequest the request object which will contain data that will
209: * be used to connect to the Cactus server side redirectors.
210: * @exception Throwable any error that occurred when calling the method
211: */
212: public void callGlobalBeginMethod(Request theRequest)
213: throws Throwable {
214: callGenericBeginMethod(theRequest, CLIENT_GLOBAL_BEGIN_METHOD);
215: }
216:
217: /**
218: * Call the client tear down up method if it exists.
219: *
220: * @param theRequest the request data that were used to open the
221: * connection.
222: * @param theResponseFactory the factory to use to return response objects.
223: * @param theResponse the Response object if it exists. Can be null in
224: * which case it is created from the response object factory
225: * @exception Throwable any error that occurred when calling the method
226: */
227: private void callGlobalEndMethod(Request theRequest,
228: ResponseObjectFactory theResponseFactory, Object theResponse)
229: throws Throwable {
230: callGenericEndMethod(theRequest, theResponseFactory,
231: CLIENT_GLOBAL_END_METHOD, theResponse);
232: }
233:
234: // Private methods ------------------------------------------------------
235:
236: /**
237: * @param theWrappedTest the pure JUnit test that we need to wrap
238: */
239: private void setWrappedTest(Test theWrappedTest) {
240: this .wrappedTest = theWrappedTest;
241: }
242:
243: /**
244: * @param theDelegatedTest the test we are delegating for
245: */
246: private void setDelegatedTest(Test theDelegatedTest) {
247: this .delegatedTest = theDelegatedTest;
248: }
249:
250: /**
251: * @return the wrapped JUnit test
252: */
253: private Test getWrappedTest() {
254: return this .wrappedTest;
255: }
256:
257: /**
258: * @return the test we are delegating for
259: */
260: private Test getDelegatedTest() {
261: return this .delegatedTest;
262: }
263:
264: /**
265: * @return the test on which we will operate. If there is a wrapped
266: * test then the returned test is the wrapped test. Otherwise we
267: * return the delegated test.
268: */
269: private Test getTest() {
270: Test activeTest;
271: if (getWrappedTest() != null) {
272: activeTest = getWrappedTest();
273: } else {
274: activeTest = getDelegatedTest();
275: }
276: return activeTest;
277: }
278:
279: /**
280: * @return the name of the test method to call without the
281: * TEST_METHOD_PREFIX prefix
282: */
283: private String getBaseMethodName() {
284: // Sanity check
285: if (!getCurrentTestName().startsWith(TEST_METHOD_PREFIX)) {
286: throw new RuntimeException("bad name ["
287: + getCurrentTestName()
288: + "]. It should start with [" + TEST_METHOD_PREFIX
289: + "].");
290: }
291:
292: return getCurrentTestName().substring(
293: TEST_METHOD_PREFIX.length());
294: }
295:
296: /**
297: * @return the name of the test begin method to call that initialize the
298: * test by initializing the <code>WebRequest</code> object
299: * for the test case.
300: */
301: private String getBeginMethodName() {
302: return BEGIN_METHOD_PREFIX + getBaseMethodName();
303: }
304:
305: /**
306: * @return the name of the test end method to call when the test has been
307: * run on the server. It can be used to verify returned headers,
308: * cookies, ...
309: */
310: private String getEndMethodName() {
311: return END_METHOD_PREFIX + getBaseMethodName();
312: }
313:
314: /**
315: * Call a begin method which takes Cactus WebRequest as parameter
316: *
317: * @param theRequest the request object which will contain data that will
318: * be used to connect to the Cactus server side redirectors.
319: * @param theMethodName the name of the begin method to call
320: * @exception Throwable any error that occurred when calling the method
321: */
322: private void callGenericBeginMethod(Request theRequest,
323: String theMethodName) throws Throwable {
324: // First, verify if a begin method exist. If one is found, verify if
325: // it has the correct signature. If not, send a warning.
326: Method[] methods = getTest().getClass().getMethods();
327:
328: for (int i = 0; i < methods.length; i++) {
329: if (methods[i].getName().equals(theMethodName)) {
330: TestCaseImplementChecker.checkAsBeginMethod(methods[i]);
331:
332: try {
333: methods[i].invoke(getTest(),
334: new Object[] { theRequest });
335:
336: break;
337: } catch (InvocationTargetException e) {
338: e.fillInStackTrace();
339: throw e.getTargetException();
340: } catch (IllegalAccessException e) {
341: e.fillInStackTrace();
342: throw e;
343: }
344: }
345: }
346: }
347:
348: /**
349: * Call the global end method. This is the method that is called after
350: * each test if it exists. It is called on the client side only.
351: *
352: * @param theRequest the request data that were used to open the
353: * connection.
354: * @param theResponseFactory the factory to use to return response objects.
355: * @param theMethodName the name of the end method to call
356: * @param theResponse the Response object if it exists. Can be null in
357: * which case it is created from the response object factory
358: * @return the created Reponse object
359: * @exception Throwable any error that occurred when calling the end method
360: * for the current test case.
361: */
362: private Object callGenericEndMethod(Request theRequest,
363: ResponseObjectFactory theResponseFactory,
364: String theMethodName, Object theResponse) throws Throwable {
365: Method methodToCall = null;
366: Object paramObject = null;
367:
368: Method[] methods = getTest().getClass().getMethods();
369:
370: for (int i = 0; i < methods.length; i++) {
371: if (methods[i].getName().equals(theMethodName)) {
372: TestCaseImplementChecker.checkAsEndMethod(methods[i]);
373:
374: paramObject = theResponse;
375:
376: if (paramObject == null) {
377: Class[] parameters = methods[i].getParameterTypes();
378: try {
379: paramObject = theResponseFactory
380: .getResponseObject(parameters[0]
381: .getName(), theRequest);
382: } catch (ClientException e) {
383: throw new ClientException("The method ["
384: + methods[i].getName()
385: + "] has a bad parameter of type ["
386: + parameters[0].getName() + "]", e);
387: }
388: }
389:
390: // Has a method to call already been found ?
391: if (methodToCall != null) {
392: fail("There can only be one method ["
393: + methods[i].getName()
394: + "] per test case. " + "Test case ["
395: + this .getCurrentTestName()
396: + "] has two at least !");
397: }
398:
399: methodToCall = methods[i];
400: }
401: }
402:
403: if (methodToCall != null) {
404: try {
405: methodToCall.invoke(getTest(),
406: new Object[] { paramObject });
407: } catch (InvocationTargetException e) {
408: e.fillInStackTrace();
409: throw e.getTargetException();
410: } catch (IllegalAccessException e) {
411: e.fillInStackTrace();
412: throw e;
413: }
414: }
415:
416: return paramObject;
417: }
418:
419: /**
420: * @return the name of the current test case being executed (it corresponds
421: * to the name of the test method with the "test" prefix removed.
422: * For example, for "testSomeTestOk" would return "someTestOk".
423: */
424: private String getCurrentTestName() {
425: return JUnitVersionHelper.getTestCaseName(getDelegatedTest());
426: }
427: }
|