001: /*
002: * The contents of this file are subject to the Sapient Public License
003: * Version 1.0 (the "License"); you may not use this file except in compliance
004: * with the License. You may obtain a copy of the License at
005: * http://carbon.sf.net/License.html.
006: *
007: * Software distributed under the License is distributed on an "AS IS" basis,
008: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
009: * the specific language governing rights and limitations under the License.
010: *
011: * The Original Code is The Carbon Component Framework.
012: *
013: * The Initial Developer of the Original Code is Sapient Corporation
014: *
015: * Copyright (C) 2003 Sapient Corporation. All Rights Reserved.
016: */
017:
018: package org.sape.carbon.core.component.test;
019:
020: import org.sape.carbon.core.bootstrap.BootStrapper;
021: import org.sape.carbon.core.component.Component;
022: import org.sape.carbon.core.component.ComponentKeeper;
023: import org.sape.carbon.core.component.ComponentNotFoundException;
024: import org.sape.carbon.core.component.lifecycle.LifecycleInterceptor;
025: import org.sape.carbon.core.component.lifecycle.LifecycleStateEnum;
026: import org.sape.carbon.core.exception.InvalidParameterException;
027:
028: import junit.extensions.ActiveTestSuite;
029: import junit.framework.Test;
030: import junit.framework.TestCase;
031: import junit.framework.TestSuite;
032:
033: /**
034: * This test case tests all the methods of the ComponentKeeper. It tests
035: * to make sure that
036: * <ul>
037: * <li>returned components references remain consistent</li>
038: * <li>returned components are in the correct state</li>
039: * <li>components are destroyed when requested</li>
040: * <li>fetch requests are blocked appropriately when a component is being
041: * created</li>
042: * <li>the keeper behaves correctly in a multi-threaded environment</li
043: * </ul>
044: *
045: * Copyright 2002 Sapient
046: * @since carbon 1.0
047: * @author Douglas Voet, Febuary 2002
048: * @version $Revision: 1.14 $($Author: ghinkl $ / $Date: 2003/10/16 13:22:00 $)
049: */
050: public class ComponentKeeperTest extends TestCase {
051: private ComponentKeeper keeper;
052:
053: public ComponentKeeperTest(String name) {
054: super (name);
055: }
056:
057: /**
058: * This method tests to make sure that the component returned from the
059: * keeper is in the LifecycleStateEnum.RUNNING state. This test assumes
060: * no other thread is creating the component.
061: */
062: public void testComponentState() {
063: LifecycleInterceptor component = (LifecycleInterceptor) this .keeper
064: .fetchComponent(EMPTY_TEST_COMPONENT);
065: if (component.getLifecycleState() != LifecycleStateEnum.RUNNING) {
066: TestCase
067: .fail("Fetched component was not in "
068: + "LifecycleStateEnum.RUNNING state as was expected");
069: }
070: }
071:
072: /**
073: * This method tests to make sure that if a component is destroyed by
074: * calling the destroyComponent lifecycle method directly, the next
075: * call to fetchComponent will return a new component, not the destroyed
076: * one
077: */
078: public void testFetchDestroyedComponent() {
079: LifecycleInterceptor component = (LifecycleInterceptor) this .keeper
080: .fetchComponent(EMPTY_TEST_COMPONENT);
081:
082: component.destroyComponent();
083:
084: component = (LifecycleInterceptor) this .keeper
085: .fetchComponent(EMPTY_TEST_COMPONENT);
086:
087: if (component.getLifecycleState() == LifecycleStateEnum.DESTROYED) {
088: TestCase
089: .fail("Fetched component was in "
090: + "LifecycleStateEnum.DESTROYED state, expected otherwise");
091: }
092: }
093:
094: /**
095: * This method tests that repeated calls the the fetchComponent method
096: * returns the same reference.
097: */
098: public void testComponentConsistency() {
099: Component component = this .keeper
100: .fetchComponent(EMPTY_TEST_COMPONENT);
101: if (component != this .keeper
102: .fetchComponent(EMPTY_TEST_COMPONENT)) {
103: TestCase.fail("Keeper did not return consistent "
104: + "component references");
105: }
106: }
107:
108: /**
109: * This test checks 2 things, that fetching a destroyed component will
110: * return a new component and that the destroyed component is in the
111: * LifecycleStateEnum.DESTROYED state.
112: */
113: public void testDestroyComponent() {
114: LifecycleInterceptor component = (LifecycleInterceptor) this .keeper
115: .fetchComponent(EMPTY_TEST_COMPONENT);
116:
117: this .keeper.destroyComponent(EMPTY_TEST_COMPONENT);
118:
119: if (component.getLifecycleState() != LifecycleStateEnum.DESTROYED) {
120: TestCase
121: .fail("Fetched component was not in "
122: + "LifecycleStateEnum.DESTROYED state as was expected");
123: }
124: if (component == this .keeper
125: .fetchComponent(EMPTY_TEST_COMPONENT)) {
126: TestCase.fail("Keeper failed to destroy a component");
127: }
128: }
129:
130: /**
131: * Same as testDestroyComponent, but repeats the test for 2 components
132: * to make sure that more than one component is destroyed.
133: */
134: public void testDestroyAllComponents() {
135: LifecycleInterceptor component1 = (LifecycleInterceptor) this .keeper
136: .fetchComponent(EMPTY_TEST_COMPONENT);
137: LifecycleInterceptor component2 = (LifecycleInterceptor) this .keeper
138: .fetchComponent(EMPTY_TEST_COMPONENT_2);
139: try {
140: this .keeper.destroyAllComponents();
141: } catch (Exception e) {
142: TestCase.fail("Caught exception while destroying "
143: + "all components: " + e);
144: }
145:
146: if ((component1.getLifecycleState() != LifecycleStateEnum.DESTROYED)
147: || (component2.getLifecycleState() != LifecycleStateEnum.DESTROYED)) {
148:
149: TestCase
150: .fail("Fetched component was not in "
151: + "LifecycleStateEnum.DESTROYED state as was expected");
152: }
153:
154: if (component1 == this .keeper
155: .fetchComponent(EMPTY_TEST_COMPONENT)
156: || component2 == this .keeper
157: .fetchComponent(EMPTY_TEST_COMPONENT_2)) {
158:
159: TestCase.fail("Keeper failed to destroy all components");
160: }
161: }
162:
163: /**
164: * This method tests that components with circular dependancies do not
165: * cause deadlocks. It does this by creating 3 components A, B, and C.
166: * A depends on B, B depends on C, and C depends on A. This request will
167: * be made on a separate thread. If the thread does not complete execution
168: * within a reasonable amount of time, the test fails.
169: */
170: public void testCircularDependency() {
171: // construct thread that requests a component that has a circular
172: // dependency
173: Thread testThread = new Thread(new Runnable() {
174: public void run() {
175: try {
176: keeper.fetchComponent(CIRCULAR_REFERENCE_A);
177: } catch (Throwable t) {
178: ComponentKeeperTest.threadException = t;
179: }
180: }
181: }, "testCircularDependency");
182: // start the thread
183: testThread.start();
184: try {
185: // wait for it to complete or to timeout
186: testThread.join(THREAD_TIMEOUT);
187: } catch (InterruptedException ie) {
188: // this should never happen so let's fail
189: throw new RuntimeException("Caught InterruptedException: "
190: + ie);
191: }
192:
193: if (ComponentKeeperTest.threadException != null) {
194: TestCase
195: .fail("Circular dependency test failed with exception "
196: + ComponentKeeperTest.threadException);
197: }
198:
199: // if the thread is still alive, fail
200: if (testThread.isAlive()) {
201: TestCase
202: .fail("Circular dependency test did not complete within "
203: + THREAD_TIMEOUT
204: + " ms, dead lock suspected");
205: }
206: }
207:
208: /**
209: * This method tests to make sure that a ComponentNotFoundException is
210: * thrown when a non-existent component is requested.
211: */
212: public void testComponentNotFound() {
213: boolean passedTest = false;
214: try {
215: this .keeper.fetchComponent(NON_EXISTENT_COMPONENT);
216: } catch (ComponentNotFoundException cnfe) {
217: passedTest = true;
218: }
219: if (!passedTest) {
220: TestCase
221: .fail("Keeper did not throw exception when attempting "
222: + "to fetch a non-existent component");
223: }
224: }
225:
226: /**
227: * This method tests to make sure that an IllegalArgumentException is
228: * thrown when null is passed as the requested component name.
229: */
230: public void testNullComponentName() {
231: boolean passedTest = false;
232: try {
233: this .keeper.fetchComponent(null);
234: } catch (InvalidParameterException ipe) {
235: passedTest = true;
236: }
237: if (!passedTest) {
238: TestCase
239: .fail("Keeper did not throw exception when attempting "
240: + "to fetch a component with a null name");
241: }
242: }
243:
244: /**
245: * Tests to make sure that creating a component does not block the
246: * retrieval of other components. This is done by kicking off 2 threads.
247: * The first will try to fetch a component that takes a long time to
248: * create. The second will fetch a component that is already created.
249: * If the second thread completes first, the test is sucessful.
250: */
251: public void testCreationNonBlocking() {
252: // make sure EMPTY_TEST_COMPONENT is created
253: LifecycleInterceptor component = (LifecycleInterceptor) this .keeper
254: .fetchComponent(EMPTY_TEST_COMPONENT);
255:
256: this .keeper.destroyComponent(LONG_INIT_COMPONENT);
257:
258: // create the thread that will take a long time to run
259: Thread longThread = new Thread(new Runnable() {
260: public void run() {
261: keeper.fetchComponent(LONG_INIT_COMPONENT);
262: }
263: });
264:
265: // create the thread that will complete quickly
266: Thread shortThread = new Thread(new Runnable() {
267: public void run() {
268: keeper.fetchComponent(EMPTY_TEST_COMPONENT);
269: }
270: });
271:
272: // start the threads, long one first
273: longThread.start();
274: // yield control to let longThread get going (hopefully)
275: Thread.yield();
276: // start the shortThread
277: shortThread.start();
278:
279: try {
280: // wait for shortThread to complete
281: shortThread.join(THREAD_TIMEOUT);
282: } catch (InterruptedException ie) {
283: // this should never happen so let's fail
284: throw new RuntimeException("Caught InterruptedException: "
285: + ie);
286: }
287:
288: if (shortThread.isAlive()) {
289: synchronized (ComponentKeeperTest.TEST_LOCK) {
290: ComponentKeeperTest.TEST_LOCK.notifyAll();
291: }
292: TestCase
293: .fail("Creation of a component blocked retrieval of "
294: + "unrelated component");
295: }
296: synchronized (ComponentKeeperTest.TEST_LOCK) {
297: ComponentKeeperTest.TEST_LOCK.notifyAll();
298: }
299: }
300:
301: public void testReturnedComponentState() {
302: this .keeper.destroyComponent(LONG_START_COMPONENT);
303:
304: Runnable fetchLongStartComponent = new Runnable() {
305: public void run() {
306: keeper.fetchComponent(LONG_START_COMPONENT);
307: }
308: };
309:
310: // create the thread that will create a component that will take a
311: // long time to start
312: Thread createThread = new Thread(fetchLongStartComponent,
313: "createThread");
314:
315: // start createThread
316: createThread.start();
317:
318: LifecycleInterceptor component = (LifecycleInterceptor) keeper
319: .fetchComponent(LONG_START_COMPONENT);
320:
321: if (component.getLifecycleState() != LifecycleStateEnum.RUNNING) {
322: TestCase
323: .fail("Component was not returned in a running state");
324: }
325: }
326:
327: /**
328: * Tests to make sure that building a component will block all requests
329: * for that component until the component is in either the
330: * LifecycleStateEnum.STOPPED or .STARTING states.
331: * This test depends on the component being requested taking a long
332: * time to load. This test makes 2 requests for a component, one on this
333: * thread, one on a new one. This thread contains the second request.
334: * The second request should complete while the new thread is still alive
335: * (i.e. the first request is still being processed). This test fails if
336: * the new thread has completed or if the component is not in the
337: * LifecycleStateEnum.STOPPED or .STARTING states.
338: */
339: public void testCreationBlocking() {
340: // make sure LONG_START_COMPONENT does not exist
341: this .keeper.destroyComponent(LONG_START_COMPONENT);
342:
343: Runnable fetchLongStartComponent = new Runnable() {
344: public void run() {
345: keeper.fetchComponent(LONG_START_COMPONENT);
346: }
347: };
348:
349: // create the thread that will create a component that will take a
350: // long time to start
351: Thread createThread = new Thread(fetchLongStartComponent,
352: "createThread");
353:
354: // create the thread that will do the same but is expected to complete
355: // earlier than createThread because it does not need to wait for the
356: // component to start
357: Thread fetchThread = new Thread(fetchLongStartComponent,
358: "fetchThread");
359:
360: // start createThread
361: createThread.start();
362: // wait for createThread to signal that it is starting
363: try {
364: synchronized (ComponentKeeperTest.TEST_LOCK) {
365: ComponentKeeperTest.TEST_LOCK.wait();
366: }
367: } catch (InterruptedException ie) {
368: // this should never happen so let's fail
369: throw new RuntimeException("Caught InterruptedException: "
370: + ie);
371: }
372:
373: try {
374: // start fetchThread which should complete
375: fetchThread.start();
376:
377: try {
378: // wait for fetchThread to complete
379: fetchThread.join(THREAD_TIMEOUT);
380: } catch (InterruptedException ie) {
381: // this should never happen so let's fail
382: throw new RuntimeException(
383: "Caught InterruptedException: " + ie);
384: }
385:
386: // check to make sure that fetchThread is done
387: if (fetchThread.isAlive()) {
388: TestCase
389: .fail("Creation of component blocked fetch of the "
390: + "same component by another thread");
391: }
392: } catch (RuntimeException re) {
393: // don't care about the exception, just want the finally block
394: throw re;
395: } finally {
396: // let createThread complete
397: synchronized (ComponentKeeperTest.TEST_LOCK) {
398: ComponentKeeperTest.TEST_LOCK.notifyAll();
399: }
400: }
401: }
402:
403: /**
404: * Fetches a component multiple times and makes sure the return references
405: * remain consistent. This test method is used as part of a multi-threaded
406: * test, each thread repeatedly asking for a different component.
407: */
408: public void testFetchComponentA() {
409: fetchComponentMultipleTimes(TEST_COMPONENT_A);
410: }
411:
412: /**
413: * Fetches a component multiple times and makes sure the return references
414: * remain consistent. This test method is used as part of a multi-threaded
415: * test, each thread repeatedly asking for a different component.
416: */
417: public void testFetchComponentB() {
418: fetchComponentMultipleTimes(TEST_COMPONENT_B);
419: }
420:
421: /**
422: * Fetches a component multiple times and makes sure the return references
423: * remain consistent. This test method is used as part of a multi-threaded
424: * test, each thread repeatedly asking for a different component.
425: */
426: public void testFetchComponentC() {
427: fetchComponentMultipleTimes(TEST_COMPONENT_C);
428: }
429:
430: private void fetchComponentMultipleTimes(String componentName) {
431: Component component = this .keeper.fetchComponent(componentName);
432: for (int i = 0; i < NUMBER_REPETITIONS; i++) {
433: if (component != this .keeper.fetchComponent(componentName)) {
434: TestCase
435: .fail("keeper is returning inconsistent component "
436: + "references");
437: }
438: }
439: }
440:
441: /** Gets a reference to the keeper for use in all tests */
442: protected void setUp() {
443: keeper = BootStrapper.getInstance().fetchComponentKeeper();
444: }
445:
446: // static members
447:
448: /**
449: * Lock object used by test components that need to wait for an extended
450: * period of time
451: */
452: public static final Object TEST_LOCK = new Object();
453:
454: private static final String EMPTY_TEST_COMPONENT = "/core/test/KeeperTestComponent1";
455: private static final String EMPTY_TEST_COMPONENT_2 = "/core/test/KeeperTestComponent2";
456: private static final String CIRCULAR_REFERENCE_A = "/core/test/KeeperCircRefTestA";
457: private static final String TEST_COMPONENT_A = "/core/test/KeeperTestComponentA";
458: private static final String TEST_COMPONENT_B = "/core/test/KeeperTestComponentB";
459: private static final String TEST_COMPONENT_C = "/core/test/KeeperTestComponentC";
460: private static final String LONG_INIT_COMPONENT = "/core/test/KeeperLongInitTestComponent";
461: private static final String LONG_START_COMPONENT = "/core/test/KeeperLongStartTestComponent";
462: private static final String NON_EXISTENT_COMPONENT = "/asasdfa";
463:
464: private static Throwable threadException = null;
465:
466: private static final long THREAD_TIMEOUT = 3000;
467: private static final int NUMBER_REPETITIONS = 100;
468:
469: /** Method called by jUnit to get all the tests in this test case */
470: public static Test suite() {
471: TestSuite masterSuite = new TestSuite();
472: // add single threaded tests
473: Test singleThreadedTests = getSingleThreadedTests();
474: if (singleThreadedTests != null) {
475: masterSuite.addTest(singleThreadedTests);
476: }
477: // add multi threaded tests
478: Test multiThreadedTests = getMultiThreadedTests();
479: if (multiThreadedTests != null) {
480: masterSuite.addTest(multiThreadedTests);
481: }
482: return masterSuite;
483: }
484:
485: /**
486: * The single threaded tests, in order, are:
487: * <ol>
488: * <li>testBadComponentName</li>
489: * <li>testNullComponentName</li>
490: * <li>testDestroyNonExistentComponent</li>
491: * <li>testComponentState</li>
492: * <li>testComponentConsistency</li>
493: * <li>testDestroyComponent</li>
494: * <li>testDestroyAllComponents</li>
495: * <li>testCircularDependency</li>
496: * <li>testCreationNonBlocking</li>
497: * <li>testCreationBlocking</li>
498: * </ol>
499: */
500: private static Test getSingleThreadedTests() {
501: TestSuite suite = new TestSuite("ComponentKeeperTest");
502:
503: suite.addTest(new ComponentKeeperTest("testComponentNotFound"));
504: suite.addTest(new ComponentKeeperTest("testNullComponentName"));
505: suite.addTest(new ComponentKeeperTest("testComponentState"));
506: suite.addTest(new ComponentKeeperTest(
507: "testComponentConsistency"));
508: suite.addTest(new ComponentKeeperTest("testDestroyComponent"));
509: suite.addTest(new ComponentKeeperTest(
510: "testDestroyAllComponents"));
511: suite
512: .addTest(new ComponentKeeperTest(
513: "testCircularDependency"));
514: suite
515: .addTest(new ComponentKeeperTest(
516: "testCreationNonBlocking"));
517: suite.addTest(new ComponentKeeperTest(
518: "testReturnedComponentState"));
519: suite.addTest(new ComponentKeeperTest(
520: "testFetchDestroyedComponent"));
521:
522: return suite;
523: }
524:
525: /**
526: * This test harness' multi-threaded test consists of running each of:
527: * <ul>
528: * <li>testFetchComponentA</li>
529: * <li>testFetchComponentB</li>
530: * <li>testFetchComponentC</li>
531: * </ul>
532: * in 3 threads.
533: */
534: private static Test getMultiThreadedTests() {
535: TestSuite suite = new ActiveTestSuite();
536:
537: addTest(suite, "testFetchComponentA", 3);
538: addTest(suite, "testFetchComponentB", 3);
539: addTest(suite, "testFetchComponentC", 3);
540:
541: return suite;
542: }
543:
544: /**
545: * This method will add the give test to the give suite the specified
546: * number of times. This is best used for multi-threaded tests where
547: * suite is an instance of ActiveTestSuite and you want to run the same test in multiple threads.
548: */
549: private static void addTest(TestSuite suite, String testName,
550: int number) {
551: for (int count = 0; count < number; count++) {
552: suite.addTest(new ComponentKeeperTest(testName));
553: }
554: }
555: }
|