1 /*
2 * ========================================================================
3 *
4 * Copyright 2001-2004 The Apache Software Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * ========================================================================
19 */
20 package org.apache.cactus.internal.client;
21
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24
25 import junit.framework.Assert;
26 import junit.framework.Test;
27
28 import org.apache.cactus.Request;
29 import org.apache.cactus.internal.util.JUnitVersionHelper;
30 import org.apache.cactus.internal.util.TestCaseImplementChecker;
31 import org.apache.cactus.spi.client.ResponseObjectFactory;
32 import org.apache.cactus.spi.client.connector.ProtocolHandler;
33 import org.apache.cactus.spi.client.connector.ProtocolState;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 /**
38 * Provides the ability to run common code before and after each test on the
39 * client side. All the methods provided are independent of any communication
40 * protocol between client side and server side (HTTP, JMS, etc). Any protocol
41 * dependent methods must be provided and implemented in the
42 * {@link ProtocolHandler} implementation class.
43 *
44 * @version $Id: ClientTestCaseCaller.java,v 1.1 2004/05/22 11:34:47 vmassol Exp $
45 */
46 public class ClientTestCaseCaller extends Assert
47 {
48 /**
49 * The prefix of a test method.
50 */
51 protected static final String TEST_METHOD_PREFIX = "test";
52
53 /**
54 * The prefix of a begin test method.
55 */
56 protected static final String BEGIN_METHOD_PREFIX = "begin";
57
58 /**
59 * The prefix of an end test method.
60 */
61 protected static final String END_METHOD_PREFIX = "end";
62
63 /**
64 * The name of the method that is called before each test on the client
65 * side (if it exists).
66 */
67 protected static final String CLIENT_GLOBAL_BEGIN_METHOD = "begin";
68
69 /**
70 * The name of the method that is called after each test on the client
71 * side (if it exists).
72 */
73 protected static final String CLIENT_GLOBAL_END_METHOD = "end";
74
75 /**
76 * The logger (only used on the client side).
77 */
78 private Log logger;
79
80 /**
81 * Pure JUnit Test Case that we are wrapping (if any)
82 */
83 private Test wrappedTest;
84
85 /**
86 * The test we are delegating for.
87 */
88 private Test delegatedTest;
89
90 /**
91 * The protocol handler to use to execute the tests on the server side.
92 */
93 private ProtocolHandler protocolHandler;
94
95 // Constructors ---------------------------------------------------------
96
97 /**
98 * @param theDelegatedTest the test we are delegating for
99 * @param theWrappedTest the test being wrapped by this delegate (or null
100 * if none)
101 * @param theProtocolHandler the protocol handler to use to execute the
102 * tests on the server side
103 */
104 public ClientTestCaseCaller(Test theDelegatedTest,
105 Test theWrappedTest, ProtocolHandler theProtocolHandler)
106 {
107 if (theDelegatedTest == null)
108 {
109 throw new IllegalStateException(
110 "The test object passed must not be null");
111 }
112
113 setDelegatedTest(theDelegatedTest);
114 setWrappedTest(theWrappedTest);
115 this.protocolHandler = theProtocolHandler;
116 }
117
118 // Public methods -------------------------------------------------------
119
120 /**
121 * Execute begin and end methods and calls the different
122 * {@link ProtocolHandler} lifecycle methods to execute the test
123 * on the server side.
124 *
125 * Note that this method is overriden from the JUnit
126 * {@link junit.framework.TestCase} class in order to prevent JUnit from
127 * calling the {@link junit.framework.TestCase#setUp()} and
128 * {@link junit.framework.TestCase#tearDown()} methods on the client side.
129 * instead we are calling the server redirector proxy and the setup and
130 * teardown methods will be executed on the server side.
131 *
132 * @exception Throwable if any error happens during the execution of
133 * the test
134 */
135 public void runTest() throws Throwable
136 {
137 Request request = this.protocolHandler.createRequest();
138
139 // Call the set up and begin methods to fill the request object
140 callGlobalBeginMethod(request);
141 callBeginMethod(request);
142
143 // Run the server test
144 ProtocolState state = this.protocolHandler.runTest(
145 getDelegatedTest(), getWrappedTest(), request);
146
147 // Call the end method
148 Object response = callEndMethod(request,
149 this.protocolHandler.createResponseObjectFactory(state));
150
151 // call the tear down method
152 callGlobalEndMethod(request,
153 this.protocolHandler.createResponseObjectFactory(state),
154 response);
155
156 this.protocolHandler.afterTest(state);
157 }
158
159 /**
160 * @return The logger used by the <code>TestCase</code> class and
161 * subclasses to perform logging.
162 */
163 public final Log getLogger()
164 {
165 return this.logger;
166 }
167
168 /**
169 * Perform client side initializations before each test, such as
170 * re-initializating the logger and printing some logging information.
171 */
172 public void runBareInit()
173 {
174 // We make sure we reinitialize The logger with the name of the
175 // current extending class so that log statements will contain the
176 // actual class name (that's why the logged instance is not static).
177 this.logger = LogFactory.getLog(this.getClass());
178
179 // Mark beginning of test on client side
180 getLogger().debug("------------- Test: " + this.getCurrentTestName());
181 }
182
183 /**
184 * Call the test case begin method.
185 *
186 * @param theRequest the request object to pass to the begin method.
187 * @exception Throwable any error that occurred when calling the begin
188 * method for the current test case.
189 */
190 public void callBeginMethod(Request theRequest) throws Throwable
191 {
192 callGenericBeginMethod(theRequest, getBeginMethodName());
193 }
194
195 /**
196 * Call the test case end method
197 *
198 * @param theRequest the request data that were used to open the
199 * connection.
200 * @param theResponseFactory the factory to use to return response objects.
201 * @return the created Reponse object
202 * @exception Throwable any error that occurred when calling the end method
203 * for the current test case.
204 */
205 public Object callEndMethod(Request theRequest,
206 ResponseObjectFactory theResponseFactory) throws Throwable
207 {
208 return callGenericEndMethod(theRequest, theResponseFactory,
209 getEndMethodName(), null);
210 }
211
212 /**
213 * Call the global begin method. This is the method that is called before
214 * each test if it exists. It is called on the client side only.
215 *
216 * @param theRequest the request object which will contain data that will
217 * be used to connect to the Cactus server side redirectors.
218 * @exception Throwable any error that occurred when calling the method
219 */
220 public void callGlobalBeginMethod(Request theRequest) throws Throwable
221 {
222 callGenericBeginMethod(theRequest, CLIENT_GLOBAL_BEGIN_METHOD);
223 }
224
225 /**
226 * Call the client tear down up method if it exists.
227 *
228 * @param theRequest the request data that were used to open the
229 * connection.
230 * @param theResponseFactory the factory to use to return response objects.
231 * @param theResponse the Response object if it exists. Can be null in
232 * which case it is created from the response object factory
233 * @exception Throwable any error that occurred when calling the method
234 */
235 private void callGlobalEndMethod(Request theRequest,
236 ResponseObjectFactory theResponseFactory, Object theResponse)
237 throws Throwable
238 {
239 callGenericEndMethod(theRequest, theResponseFactory,
240 CLIENT_GLOBAL_END_METHOD, theResponse);
241 }
242
243 // Private methods ------------------------------------------------------
244
245 /**
246 * @param theWrappedTest the pure JUnit test that we need to wrap
247 */
248 private void setWrappedTest(Test theWrappedTest)
249 {
250 this.wrappedTest = theWrappedTest;
251 }
252
253 /**
254 * @param theDelegatedTest the test we are delegating for
255 */
256 private void setDelegatedTest(Test theDelegatedTest)
257 {
258 this.delegatedTest = theDelegatedTest;
259 }
260
261 /**
262 * @return the wrapped JUnit test
263 */
264 private Test getWrappedTest()
265 {
266 return this.wrappedTest;
267 }
268
269 /**
270 * @return the test we are delegating for
271 */
272 private Test getDelegatedTest()
273 {
274 return this.delegatedTest;
275 }
276
277 /**
278 * @return the test on which we will operate. If there is a wrapped
279 * test then the returned test is the wrapped test. Otherwise we
280 * return the delegated test.
281 */
282 private Test getTest()
283 {
284 Test activeTest;
285 if (getWrappedTest() != null)
286 {
287 activeTest = getWrappedTest();
288 }
289 else
290 {
291 activeTest = getDelegatedTest();
292 }
293 return activeTest;
294 }
295
296 /**
297 * @return the name of the test method to call without the
298 * TEST_METHOD_PREFIX prefix
299 */
300 private String getBaseMethodName()
301 {
302 // Sanity check
303 if (!getCurrentTestName().startsWith(TEST_METHOD_PREFIX))
304 {
305 throw new RuntimeException("bad name ["
306 + getCurrentTestName()
307 + "]. It should start with ["
308 + TEST_METHOD_PREFIX + "].");
309 }
310
311 return getCurrentTestName().substring(
312 TEST_METHOD_PREFIX.length());
313 }
314
315 /**
316 * @return the name of the test begin method to call that initialize the
317 * test by initializing the <code>WebRequest</code> object
318 * for the test case.
319 */
320 private String getBeginMethodName()
321 {
322 return BEGIN_METHOD_PREFIX + getBaseMethodName();
323 }
324
325 /**
326 * @return the name of the test end method to call when the test has been
327 * run on the server. It can be used to verify returned headers,
328 * cookies, ...
329 */
330 private String getEndMethodName()
331 {
332 return END_METHOD_PREFIX + getBaseMethodName();
333 }
334
335 /**
336 * Call a begin method which takes Cactus WebRequest as parameter
337 *
338 * @param theRequest the request object which will contain data that will
339 * be used to connect to the Cactus server side redirectors.
340 * @param theMethodName the name of the begin method to call
341 * @exception Throwable any error that occurred when calling the method
342 */
343 private void callGenericBeginMethod(Request theRequest,
344 String theMethodName) throws Throwable
345 {
346 // First, verify if a begin method exist. If one is found, verify if
347 // it has the correct signature. If not, send a warning.
348 Method[] methods = getTest().getClass().getMethods();
349
350 for (int i = 0; i < methods.length; i++)
351 {
352 if (methods[i].getName().equals(theMethodName))
353 {
354 TestCaseImplementChecker.checkAsBeginMethod(methods[i]);
355
356 try
357 {
358 methods[i].invoke(getTest(), new Object[] {theRequest});
359
360 break;
361 }
362 catch (InvocationTargetException e)
363 {
364 e.fillInStackTrace();
365 throw e.getTargetException();
366 }
367 catch (IllegalAccessException e)
368 {
369 e.fillInStackTrace();
370 throw e;
371 }
372 }
373 }
374 }
375
376 /**
377 * Call the global end method. This is the method that is called after
378 * each test if it exists. It is called on the client side only.
379 *
380 * @param theRequest the request data that were used to open the
381 * connection.
382 * @param theResponseFactory the factory to use to return response objects.
383 * @param theMethodName the name of the end method to call
384 * @param theResponse the Response object if it exists. Can be null in
385 * which case it is created from the response object factory
386 * @return the created Reponse object
387 * @exception Throwable any error that occurred when calling the end method
388 * for the current test case.
389 */
390 private Object callGenericEndMethod(Request theRequest,
391 ResponseObjectFactory theResponseFactory, String theMethodName,
392 Object theResponse) throws Throwable
393 {
394 Method methodToCall = null;
395 Object paramObject = null;
396
397 Method[] methods = getTest().getClass().getMethods();
398
399 for (int i = 0; i < methods.length; i++)
400 {
401 if (methods[i].getName().equals(theMethodName))
402 {
403 TestCaseImplementChecker.checkAsEndMethod(methods[i]);
404
405 paramObject = theResponse;
406
407 if (paramObject == null)
408 {
409 Class[] parameters = methods[i].getParameterTypes();
410 try
411 {
412 paramObject = theResponseFactory.getResponseObject(
413 parameters[0].getName(), theRequest);
414 }
415 catch (ClientException e)
416 {
417 throw new ClientException("The method ["
418 + methods[i].getName()
419 + "] has a bad parameter of type ["
420 + parameters[0].getName() + "]", e);
421 }
422 }
423
424 // Has a method to call already been found ?
425 if (methodToCall != null)
426 {
427 fail("There can only be one method ["
428 + methods[i].getName() + "] per test case. "
429 + "Test case [" + this.getCurrentTestName()
430 + "] has two at least !");
431 }
432
433 methodToCall = methods[i];
434 }
435 }
436
437 if (methodToCall != null)
438 {
439 try
440 {
441 methodToCall.invoke(getTest(), new Object[] {paramObject});
442 }
443 catch (InvocationTargetException e)
444 {
445 e.fillInStackTrace();
446 throw e.getTargetException();
447 }
448 catch (IllegalAccessException e)
449 {
450 e.fillInStackTrace();
451 throw e;
452 }
453 }
454
455 return paramObject;
456 }
457
458 /**
459 * @return the name of the current test case being executed (it corresponds
460 * to the name of the test method with the "test" prefix removed.
461 * For example, for "testSomeTestOk" would return "someTestOk".
462 */
463 private String getCurrentTestName()
464 {
465 return JUnitVersionHelper.getTestCaseName(getDelegatedTest());
466 }
467 }