001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.lockmanager.impl;
006:
007: import EDU.oswego.cs.dl.util.concurrent.CyclicBarrier;
008:
009: import com.tc.logging.NullTCLogger;
010: import com.tc.management.ClientLockStatManager;
011: import com.tc.management.L2LockStatsManager;
012: import com.tc.object.lockmanager.api.LockID;
013: import com.tc.object.lockmanager.api.LockLevel;
014: import com.tc.object.lockmanager.api.LockRequest;
015: import com.tc.object.lockmanager.api.ThreadID;
016: import com.tc.object.lockmanager.api.WaitListener;
017: import com.tc.object.session.TestSessionManager;
018: import com.tc.object.tx.WaitInvocation;
019: import com.tc.objectserver.lockmanager.api.LockHolder;
020: import com.tc.objectserver.lockmanager.api.LockMBean;
021: import com.tc.objectserver.lockmanager.api.NullChannelManager;
022: import com.tc.objectserver.lockmanager.api.ServerLockRequest;
023: import com.tc.objectserver.lockmanager.api.Waiter;
024: import com.tc.objectserver.lockmanager.impl.LockManagerImpl;
025: import com.tc.util.Assert;
026:
027: import java.util.HashSet;
028: import java.util.Iterator;
029:
030: import junit.framework.TestCase;
031:
032: public class ClientServerLockManagerTest extends TestCase {
033:
034: private ClientLockManagerImpl clientLockManager;
035: private LockManagerImpl serverLockManager;
036: private ClientServerLockManagerGlue glue;
037: private TestSessionManager sessionManager;
038:
039: protected void setUp() throws Exception {
040: super .setUp();
041: sessionManager = new TestSessionManager();
042: glue = new ClientServerLockManagerGlue(sessionManager);
043: clientLockManager = new ClientLockManagerImpl(
044: new NullTCLogger(), glue, sessionManager,
045: ClientLockStatManager.NULL_CLIENT_LOCK_STAT_MANAGER);
046:
047: serverLockManager = new LockManagerImpl(
048: new NullChannelManager(),
049: L2LockStatsManager.NULL_LOCK_STATS_MANAGER);
050: serverLockManager
051: .setLockPolicy(LockManagerImpl.ALTRUISTIC_LOCK_POLICY);
052: glue.set(clientLockManager, serverLockManager);
053: }
054:
055: public void testRWServer() {
056: final LockID lockID1 = new LockID("1");
057: final LockID lockID2 = new LockID("2");
058: final ThreadID tx1 = new ThreadID(1);
059: final ThreadID tx2 = new ThreadID(2);
060: final ThreadID tx3 = new ThreadID(3);
061:
062: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
063: clientLockManager.lock(lockID1, tx3, LockLevel.READ);
064: clientLockManager.lock(lockID2, tx2, LockLevel.READ);
065: //clientLockManager.lock(lockID2, tx2, LockLevel.WRITE); // Upgrade
066:
067: clientLockManager.pause();
068: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
069:
070: LockManagerImpl server2 = glue.restartServer();
071:
072: LockMBean[] lockBeans2 = server2.getAllLocks();
073: if (!equals(lockBeans1, lockBeans2)) {
074: throw new AssertionError("The locks are not the same");
075: }
076: }
077:
078: public void testWRServer() {
079: final LockID lockID1 = new LockID("1");
080: final LockID lockID2 = new LockID("2");
081: final ThreadID tx1 = new ThreadID(1);
082: final ThreadID tx2 = new ThreadID(2);
083: final ThreadID tx3 = new ThreadID(3);
084:
085: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
086: clientLockManager.lock(lockID1, tx3, LockLevel.READ);
087: clientLockManager.lock(lockID2, tx2, LockLevel.WRITE);
088: clientLockManager.lock(lockID2, tx2, LockLevel.READ);
089:
090: clientLockManager.pause();
091: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
092:
093: LockManagerImpl server2 = glue.restartServer();
094:
095: LockMBean[] lockBeans2 = server2.getAllLocks();
096: if (!equals(lockBeans1, lockBeans2)) {
097: throw new AssertionError("The locks are not the same");
098: }
099: }
100:
101: public void testLockWaitWriteServer() throws Exception {
102: final LockID lockID1 = new LockID("1");
103: final ThreadID tx1 = new ThreadID(1);
104:
105: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
106:
107: final CyclicBarrier barrier = new CyclicBarrier(2);
108: Thread waitCallThread = new Thread() {
109:
110: public void run() {
111: try {
112: clientLockManager.wait(lockID1, tx1,
113: new WaitInvocation(), new Object(),
114: new WaitListener() {
115:
116: public void handleWaitEvent() {
117: try {
118: barrier.barrier();
119: } catch (Exception e) {
120: e.printStackTrace();
121: throw new AssertionError(e);
122: }
123: }
124: });
125: } catch (InterruptedException ie) {
126: handleExceptionForTest(ie);
127: }
128: }
129: };
130: waitCallThread.start();
131: barrier.barrier();
132:
133: clientLockManager.pause();
134: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
135:
136: LockManagerImpl server2 = glue.restartServer();
137:
138: LockMBean[] lockBeans2 = server2.getAllLocks();
139: if (!equals(lockBeans1, lockBeans2)) {
140: throw new AssertionError("The locks are not the same");
141: }
142: }
143:
144: public void testWaitWRServer() {
145: final LockID lockID1 = new LockID("1");
146: final ThreadID tx1 = new ThreadID(1);
147:
148: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
149: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
150: Thread waitCallThread = new Thread() {
151:
152: public void run() {
153: try {
154: clientLockManager.wait(lockID1, tx1,
155: new WaitInvocation(), new Object(),
156: new WaitListener() {
157:
158: public void handleWaitEvent() {
159: // Formatter
160: }
161: });
162: } catch (InterruptedException ie) {
163: handleExceptionForTest(ie);
164: }
165: }
166: };
167: waitCallThread.start();
168: sleep(1000l);
169:
170: clientLockManager.pause();
171: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
172:
173: LockManagerImpl server2 = glue.restartServer();
174:
175: LockMBean[] lockBeans2 = server2.getAllLocks();
176: if (!equals(lockBeans1, lockBeans2)) {
177: throw new AssertionError("The locks are not the same");
178: }
179: }
180:
181: public void testWaitNotifyRWServer() {
182: final LockID lockID1 = new LockID("1");
183: final ThreadID tx1 = new ThreadID(1);
184: final ThreadID tx2 = new ThreadID(2);
185:
186: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
187:
188: Thread waitCallThread = new Thread() {
189:
190: public void run() {
191: try {
192: clientLockManager.wait(lockID1, tx1,
193: new WaitInvocation(), new Object(),
194: new WaitListener() {
195:
196: public void handleWaitEvent() {
197: // Formatter
198: }
199: });
200: } catch (InterruptedException ie) {
201: handleExceptionForTest(ie);
202: }
203: }
204: };
205: waitCallThread.start();
206: sleep(1000l);
207:
208: clientLockManager.lock(lockID1, tx2, LockLevel.WRITE);
209: /*
210: * Since this call is no longer in Lock manager, forced to call the server lock manager directly
211: * clientLockManager.notify(lockID1,tx2, true);
212: */
213: glue.notify(lockID1, tx2, true);
214: clientLockManager.unlock(lockID1, tx2);
215: sleep(1000l);
216:
217: clientLockManager.pause();
218: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
219:
220: LockManagerImpl server2 = glue.restartServer();
221:
222: LockMBean[] lockBeans2 = server2.getAllLocks();
223: if (!equals(lockBeans1, lockBeans2)) {
224: throw new AssertionError("The locks are not the same");
225: }
226: }
227:
228: public void testWaitNotifyRWClientServer() {
229: final LockID lockID1 = new LockID("1");
230: final ThreadID tx1 = new ThreadID(1);
231: final ThreadID tx2 = new ThreadID(2);
232:
233: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
234:
235: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
236:
237: Thread waitCallThread = new Thread() {
238:
239: public void run() {
240: try {
241: clientLockManager.wait(lockID1, tx1,
242: new WaitInvocation(), new Object(),
243: new WaitListener() {
244:
245: public void handleWaitEvent() {
246: // Formatter
247: }
248: });
249: } catch (InterruptedException ie) {
250: handleExceptionForTest(ie);
251: }
252: }
253: };
254: waitCallThread.start();
255: sleep(1000l);
256:
257: clientLockManager.lock(lockID1, tx2, LockLevel.WRITE);
258: /*
259: * Since this call is no longer in Lock manager, forced to call the server lock manager directly
260: * clientLockManager.notify(lockID1,tx2, true);
261: */
262: glue.notify(lockID1, tx2, true);
263: clientLockManager.unlock(lockID1, tx2);
264: sleep(1000l);
265:
266: clientLockManager.pause();
267: clientLockManager.starting();
268: boolean found = false;
269: for (Iterator i = clientLockManager.addAllHeldLocksTo(
270: new HashSet()).iterator(); i.hasNext();) {
271: LockRequest request = (LockRequest) i.next();
272: if (request.lockID().equals(lockID1)
273: && request.threadID().equals(tx1)) {
274: if (LockLevel.isRead(request.lockLevel())) {
275: // formater
276: throw new AssertionError(
277: "Should not have READ lock level.");
278: }
279: found = true;
280: break;
281: }
282: }
283: if (!found) {
284: // formatter
285: throw new AssertionError(
286: "Didn't find the lock I am looking for");
287: }
288: LockMBean[] lockBeans2 = serverLockManager.getAllLocks();
289: if (!equals(lockBeans1, lockBeans2)) {
290: throw new AssertionError("The locks are not the same");
291: }
292: }
293:
294: public void testWaitNotifyWRClientServer() {
295: final LockID lockID1 = new LockID("1");
296: final ThreadID tx1 = new ThreadID(1);
297: final ThreadID tx2 = new ThreadID(2);
298:
299: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
300: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
301:
302: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
303:
304: Thread waitCallThread = new Thread() {
305:
306: public void run() {
307: try {
308: clientLockManager.wait(lockID1, tx1,
309: new WaitInvocation(), new Object(),
310: new WaitListener() {
311:
312: public void handleWaitEvent() {
313: // Formatter
314: }
315: });
316: } catch (InterruptedException ie) {
317: handleExceptionForTest(ie);
318: }
319: }
320: };
321: waitCallThread.start();
322: sleep(1000l);
323:
324: clientLockManager.lock(lockID1, tx2, LockLevel.WRITE);
325: /*
326: * Since this call is no longer in Lock manager, forced to call the server lock manager directly
327: * clientLockManager.notify(lockID1,tx2, true);
328: */
329: glue.notify(lockID1, tx2, true);
330: clientLockManager.unlock(lockID1, tx2);
331: sleep(1000l);
332:
333: clientLockManager.pause();
334: clientLockManager.starting();
335: boolean found = false;
336: for (Iterator i = clientLockManager.addAllHeldLocksTo(
337: new HashSet()).iterator(); i.hasNext();) {
338: LockRequest request = (LockRequest) i.next();
339: if (request.lockID().equals(lockID1)
340: && request.threadID().equals(tx1)) {
341: // Since READ was given locally only WRITE gets here
342: if (LockLevel.isRead(request.lockLevel())
343: || !LockLevel.isWrite(request.lockLevel())) {
344: // formater
345: throw new AssertionError(
346: "Server Lock Level is not WRITE only on the client side");
347: }
348: found = true;
349: break;
350: }
351: }
352: Assert.assertTrue(clientLockManager.haveLock(lockID1, tx1,
353: LockLevel.READ | LockLevel.WRITE));
354: if (!found) {
355: // formatter
356: throw new AssertionError(
357: "Didn't find the lock I am looking for");
358: }
359: LockMBean[] lockBeans2 = serverLockManager.getAllLocks();
360: if (!equals(lockBeans1, lockBeans2)) {
361: throw new AssertionError("The locks are not the same");
362: }
363: }
364:
365: public void testPendingWaitNotifiedRWClientServer() {
366: final LockID lockID1 = new LockID("1");
367: final ThreadID tx1 = new ThreadID(1);
368: final ThreadID tx2 = new ThreadID(2);
369:
370: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
371:
372: Thread waitCallThread = new Thread() {
373:
374: public void run() {
375: try {
376: clientLockManager.wait(lockID1, tx1,
377: new WaitInvocation(), new Object(),
378: new WaitListener() {
379:
380: public void handleWaitEvent() {
381: // Formatter
382: }
383: });
384: } catch (InterruptedException ie) {
385: handleExceptionForTest(ie);
386: }
387: }
388: };
389: waitCallThread.start();
390: sleep(1000l);
391:
392: clientLockManager.lock(lockID1, tx2, LockLevel.WRITE);
393: /*
394: * Since this call is no longer in Lock manager, forced to call the server lock manager directly
395: * clientLockManager.notify(lockID1,tx2, true);
396: */
397: glue.notify(lockID1, tx2, true);
398: sleep(1000l);
399:
400: clientLockManager.pause();
401: clientLockManager.starting();
402: boolean found = false;
403: for (Iterator i = clientLockManager.addAllHeldLocksTo(
404: new HashSet()).iterator(); i.hasNext();) {
405: LockRequest request = (LockRequest) i.next();
406: if (request.lockID().equals(lockID1)
407: && request.threadID().equals(tx2)) {
408: if (LockLevel.isRead(request.lockLevel())
409: || !LockLevel.isWrite(request.lockLevel())) {
410: // formater
411: throw new AssertionError(
412: "Server Lock Level is not WRITE only on tx2 the client side");
413: }
414: found = true;
415: break;
416: }
417: }
418: if (!found) {
419: // formatter
420: throw new AssertionError(
421: "Didn't find the lock I am looking for");
422: }
423:
424: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
425:
426: LockManagerImpl server2 = glue.restartServer();
427:
428: LockMBean[] lockBeans2 = server2.getAllLocks();
429: if (!equals(lockBeans1, lockBeans2)) {
430: throw new AssertionError("The locks are not the same");
431: }
432: }
433:
434: public void testPendingWaitNotifiedWRClientServer() {
435: final LockID lockID1 = new LockID("1");
436: final ThreadID tx1 = new ThreadID(1);
437: final ThreadID tx2 = new ThreadID(2);
438:
439: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
440: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
441:
442: Thread waitCallThread = new Thread() {
443:
444: public void run() {
445: try {
446: clientLockManager.wait(lockID1, tx1,
447: new WaitInvocation(), new Object(),
448: new WaitListener() {
449:
450: public void handleWaitEvent() {
451: // Formatter
452: }
453: });
454: } catch (InterruptedException ie) {
455: handleExceptionForTest(ie);
456: }
457: }
458: };
459: waitCallThread.start();
460: sleep(1000l);
461:
462: clientLockManager.lock(lockID1, tx2, LockLevel.WRITE);
463: /*
464: * Since this call is no longer in Lock manager, forced to call the server lock manager directly
465: * clientLockManager.notify(lockID1,tx2, true);
466: */
467: glue.notify(lockID1, tx2, true);
468: sleep(1000l);
469:
470: clientLockManager.pause();
471: clientLockManager.starting();
472: boolean found = false;
473: for (Iterator i = clientLockManager.addAllHeldLocksTo(
474: new HashSet()).iterator(); i.hasNext();) {
475: LockRequest request = (LockRequest) i.next();
476: if (request.lockID().equals(lockID1)
477: && request.threadID().equals(tx2)) {
478: if (LockLevel.isRead(request.lockLevel())
479: || !LockLevel.isWrite(request.lockLevel())) {
480: // formater
481: throw new AssertionError(
482: "Server Lock Level is not WRITE only on tx2 the client side");
483: }
484: found = true;
485: break;
486: }
487: }
488: if (!found) {
489: // formatter
490: throw new AssertionError(
491: "Didn't find the lock I am looking for");
492: }
493:
494: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
495: LockManagerImpl server2 = glue.restartServer();
496:
497: LockMBean[] lockBeans2 = server2.getAllLocks();
498: if (!equals(lockBeans1, lockBeans2)) {
499: throw new AssertionError("The locks are not the same");
500: }
501: }
502:
503: public void testPendingRequestClientServer() {
504: final LockID lockID1 = new LockID("1");
505: final ThreadID tx1 = new ThreadID(1);
506:
507: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
508: clientLockManager.lock(lockID1, tx1, LockLevel.READ);
509:
510: Thread pendingLockRequestThread = new Thread() {
511:
512: public void run() {
513: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
514: }
515: };
516: pendingLockRequestThread.start();
517: sleep(1000l);
518:
519: clientLockManager.pause();
520:
521: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
522: LockManagerImpl server2 = glue.restartServer();
523:
524: LockMBean[] lockBeans2 = server2.getAllLocks();
525: if (!equals(lockBeans1, lockBeans2)) {
526: throw new AssertionError("The locks are not the same");
527: }
528: }
529:
530: public void testWRClient() {
531: final LockID lockID1 = new LockID("1");
532: final ThreadID tx1 = new ThreadID(1);
533:
534: clientLockManager.lock(lockID1, tx1, LockLevel.WRITE);
535: clientLockManager.lock(lockID1, tx1, LockLevel.READ); // Local Upgrade
536: clientLockManager.unlock(lockID1, tx1); // should release READ
537:
538: clientLockManager.pause();
539: clientLockManager.starting();
540:
541: boolean found = false;
542: for (Iterator i = clientLockManager.addAllHeldLocksTo(
543: new HashSet()).iterator(); i.hasNext();) {
544: LockRequest request = (LockRequest) i.next();
545: if (request.lockID().equals(lockID1)
546: && request.threadID().equals(tx1)) {
547: if (LockLevel.isRead(request.lockLevel())
548: || !LockLevel.isWrite(request.lockLevel())) {
549: //
550: throw new AssertionError(
551: "Lock Level is not WRITE only");
552: }
553: found = true;
554: break;
555: }
556: }
557: if (!found) {
558: throw new AssertionError(
559: "Didn't find the lock I am looking for");
560: }
561: }
562:
563: public void testConcurrentLocksServerRestart() {
564: final LockID lockID1 = new LockID("1");
565: final ThreadID tx1 = new ThreadID(1);
566: final ThreadID tx2 = new ThreadID(2);
567:
568: clientLockManager.lock(lockID1, tx1, LockLevel.CONCURRENT);
569: clientLockManager.lock(lockID1, tx2, LockLevel.CONCURRENT);
570:
571: clientLockManager.pause();
572: LockMBean[] lockBeans1 = serverLockManager.getAllLocks();
573:
574: LockManagerImpl server2 = glue.restartServer();
575:
576: LockMBean[] lockBeans2 = server2.getAllLocks();
577: if (!equals(lockBeans1, lockBeans2)) {
578: throw new AssertionError("The locks are not the same");
579: }
580: }
581:
582: private boolean equals(LockMBean[] lockBeans1,
583: LockMBean[] lockBeans2) {
584: if (lockBeans1.length != lockBeans2.length) {
585: return false;
586: }
587: for (int i = 0; i < lockBeans1.length; i++) {
588: String lockName1 = lockBeans1[i].getLockName();
589: boolean found = false;
590: for (int j = 0; j < lockBeans2.length; j++) {
591: String lockName2 = lockBeans2[j].getLockName();
592: if (lockName1.equals(lockName2)) {
593: if (!equals(lockBeans1[i], lockBeans2[j])) {
594: return false;
595: }
596: found = true;
597: break;
598: }
599: }
600: if (!found) {
601: return false;
602: }
603: }
604: return true;
605: }
606:
607: private boolean equals(LockMBean bean1, LockMBean bean2) {
608: return equals(bean1.getHolders(), bean2.getHolders())
609: && equals(bean1.getPendingRequests(), bean2
610: .getPendingRequests())
611: && equals(bean1.getWaiters(), bean2.getWaiters());
612: }
613:
614: private boolean equals(Waiter[] waiters1, Waiter[] waiters2) {
615: if (waiters1 == null && waiters2 == null) {
616: return true;
617: } else if (waiters1 == null || waiters2 == null
618: || waiters1.length != waiters2.length) {
619: return false;
620: }
621: for (int i = 0; i < waiters1.length; i++) {
622: boolean found = false;
623: for (int j = 0; j < waiters2.length; j++) {
624: if (waiters1[i].getThreadID().equals(
625: waiters2[j].getThreadID())) {
626: // XXX :: Should I do this -- Come back
627: /*
628: * if ( waiters1[i].getStartTime() != waiters2[j].getStartTime() ||
629: * waiters1[i].getWaitInvocation().equals(waiters2[j].getWaitInvocation())) { System.err.println("Not equal - " +
630: * waiters1[i].getStartTime() + " - " + waiters2[j].getStartTime()); System.err.println("Not equal - " +
631: * waiters1[i].getWaitInvocation() + " - " + waiters2[j].getWaitInvocation()); return false; }
632: */
633: found = true;
634: break;
635: }
636: }
637: if (!found) {
638: return false;
639: }
640: }
641: return true;
642: }
643:
644: private boolean equals(ServerLockRequest[] pendingRequests1,
645: ServerLockRequest[] pendingRequests2) {
646: if (pendingRequests1 == null && pendingRequests2 == null) {
647: return true;
648: } else if (pendingRequests1 == null || pendingRequests2 == null
649: || pendingRequests1.length != pendingRequests2.length) {
650: // for formatter
651: return false;
652: }
653: for (int i = 0; i < pendingRequests1.length; i++) {
654: boolean found = false;
655: for (int j = 0; j < pendingRequests2.length; j++) {
656: if (pendingRequests1[i].getThreadID().equals(
657: pendingRequests2[j].getThreadID())) {
658: if (!pendingRequests1[i].getLockLevel().equals(
659: pendingRequests2[j].getLockLevel())) {
660: System.err.println("Not equal - "
661: + pendingRequests1[i].getLockLevel()
662: + " - "
663: + pendingRequests2[j].getLockLevel());
664: return false;
665: }
666: found = true;
667: break;
668: }
669: }
670: if (!found) {
671: return false;
672: }
673: }
674: return true;
675: }
676:
677: private boolean equals(LockHolder[] holders1, LockHolder[] holders2) {
678: if (holders1 == null && holders2 == null) {
679: return true;
680: } else if (holders1 == null || holders2 == null
681: || holders1.length != holders2.length) {
682: return false;
683: }
684: for (int i = 0; i < holders1.length; i++) {
685: boolean found = false;
686: for (int j = 0; j < holders2.length; j++) {
687: if (holders1[i].getThreadID().equals(
688: holders2[j].getThreadID())) {
689: if (!holders1[i].getLockLevel().equals(
690: holders2[j].getLockLevel())) {
691: System.out.println("Not equal - " + holders1[i]
692: + " - " + holders2[j]);
693: return false;
694: }
695: found = true;
696: break;
697: }
698: }
699: if (!found) {
700: return false;
701: }
702: }
703: return true;
704: }
705:
706: private void sleep(long l) {
707: try {
708: Thread.sleep(l);
709: } catch (InterruptedException e) {
710: // NOP
711: }
712: }
713:
714: private void handleExceptionForTest(Exception e) {
715: e.printStackTrace();
716: throw new AssertionError(e);
717: }
718:
719: protected void tearDown() throws Exception {
720: glue.stop();
721: super.tearDown();
722: }
723: }
|