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