001: /*
002:
003: Derby - Class org.apache.derbyTesting.functionTests.harness.MultiTest
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to You under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derbyTesting.functionTests.harness;
023:
024: import java.io.IOException;
025: import java.io.File;
026: import java.io.FileOutputStream;
027: import java.io.FileNotFoundException;
028: import java.io.InputStream;
029: import java.io.FileInputStream;
030: import java.io.BufferedInputStream;
031: import java.util.Vector;
032: import java.util.Enumeration;
033: import org.apache.derby.impl.tools.ij.*;
034:
035: import org.apache.derby.iapi.tools.i18n.LocalizedResource;
036: import org.apache.derby.iapi.tools.i18n.LocalizedOutput;
037:
038: /**
039: * MultiTest is a multiuser test harness. It
040: * runs multiple threads with differing scripts
041: * against an embedded server. For syntax see
042: * grammar.jj
043: *
044: * Tests are ended as soon as an unexpected error
045: * has occurred or after the specified test duration
046: * has transpired. The main loop ends by explicitly
047: * quitting (i.e. doing a System.exit()).
048: *
049: * Deprecated APIs: this test uses AppStreamWriter instead
050: * of the preferred AppStreamWriter. This is because utilMain()
051: * uses AppStreamWriter (deliberately, i think) so since
052: * that is called from this, there you have it.
053: */
054:
055: public class MultiTest {
056:
057: private static final int MAX_WAIT_FOR_COMPLETION = 180;
058:
059: private static mtTestSuite suite;
060: private static LocalizedOutput log;
061: private static String inputDir;
062: private static String outputDir;
063: private static String testName;
064:
065: public MultiTest() {
066: };
067:
068: public static void syntax() {
069: System.out.println("Syntax:" + "\n\t <file>\t- the cmd file"
070: + "\n\t[-o <dir>]\t-the output directory"
071: + "\n\t[-i <dir>]\t-the input directory");
072: }
073:
074: /**
075: ** Main loop
076: @exception IOException thrown on error
077: @exception ParseException thrown on error
078: @exception FileNotFoundException thrown on error
079: */
080: public static void main(String[] args) throws IOException,
081: ParseException, FileNotFoundException {
082: String cmdFilePath;
083: InputStream in;
084: String cmdFile;
085: mtTester[] testers;
086: int i;
087: int max;
088:
089: if ((cmdFile = util.getFileArg(args)) == null) {
090: syntax();
091: return;
092: }
093:
094: LocalizedResource.getInstance();
095:
096: testName = getTestName(cmdFile);
097: inputDir = util.getArg("-i", args);
098: outputDir = util.getArg("-o", args);
099:
100: /*
101: ** If cmdfile doesn't have a path, prepend
102: ** inputDir
103: */
104: cmdFilePath = ((inputDir != null) && (cmdFile.indexOf("/") == -1)) ? (inputDir
105: + "/" + cmdFile)
106: : cmdFile;
107: try {
108: in = new BufferedInputStream(new FileInputStream(
109: cmdFilePath), utilMain.BUFFEREDFILESIZE);
110: } catch (FileNotFoundException e) {
111: System.out
112: .println("MultiTest ERROR: config file not found: "
113: + cmdFile);
114: return;
115: }
116: mtGrammar parser = new mtGrammar(in);
117: suite = parser.grammarStatement();
118: suite.setRoot(inputDir);
119: suite.init();
120:
121: log = openFile(outputDir, testName + ".log");
122:
123: try {
124: seqRunCases(suite.getInitCases(), "initialization",
125: inputDir, outputDir);
126: } catch (ijFatalException e) {
127: System.exit(1);
128: }
129:
130: max = suite.getNumThreads();
131: System.out.println("...running with " + max + " threads");
132: testers = new mtTester[max];
133:
134: // create the testers
135: for (i = 0; i < max; i++) {
136: String tester = "Tester" + (i + 1);
137: try {
138: LocalizedOutput out = openFile(outputDir, tester
139: + ".out");
140: testers[i] = new mtTester(tester, suite, out, log);
141: } catch (IOException e) {
142: System.out
143: .println("MultiTest ERROR: unable open output file "
144: + e);
145: return;
146: }
147: }
148:
149: long duration = execTesters(testers);
150:
151: log.println("");
152: log.println("test ran " + duration + " ms");
153: log.println("total memory is "
154: + Runtime.getRuntime().totalMemory());
155: log.println("free memory is "
156: + Runtime.getRuntime().freeMemory());
157: // Delete the .out files for Testers that did not report errors.
158: for (i = 0; i < max; i++) {
159: if (testers[i].noFailure()) {
160: log.println("Deleting " + "Tester" + (i + 1) + ".out"
161: + "(" + outputDir + ")");
162: File out = new File(outputDir, "Tester" + (i + 1)
163: + ".out");
164: out.delete();
165: } else {
166: log.println("Tester" + (i + 1) + " failed.");
167: }
168: }
169:
170: System.exit(0);
171: }
172:
173: /*
174: **
175: ** NOTE ON OUT OF MEMORY PROBLEMS: in theory
176: ** when the VM runs out of memory an OutOfMemoryException
177: ** should be thrown by the runtime, but unfortunately, that
178: ** doesn't always seem to be the case. When running this
179: ** program the Testers just wind up hanging on memory
180: ** allocation if there is insufficient memory. To combat
181: ** this we try to manually stop each thread, but when
182: ** there is no memory, this doesn't seem to do anything
183: ** either. Also, we grab some memory up front and release
184: ** that after telling the threads to stop themselves.
185: */
186: private static long execTesters(mtTester[] testers)
187: throws FileNotFoundException, IOException {
188: boolean interrupted = false;
189: boolean allWereAlive = true;
190: int i;
191: long duration = 0;
192: int max = testers.length;
193: Thread[] threads;
194: byte[] extraMemory;
195:
196: // new thread group
197: ThreadGroup tg = new ThreadGroup("workers");
198: //tg.allowThreadSuspension(false);
199:
200: // grab start time
201: long start = System.currentTimeMillis();
202: long runTime = suite.getTimeMillis();
203: System.out.println("...running duration " + suite.getTime());
204:
205: // grab some memory to make stopping easier
206: extraMemory = new byte[4096];
207:
208: threads = new Thread[max];
209: // run them
210: for (i = 0; i < max; i++) {
211: threads[i] = new Thread(tg, testers[i]);
212: threads[i].start();
213: }
214:
215: // loop sleeping 800ms a bite.
216: while (((duration = (System.currentTimeMillis() - start)) < runTime)
217: && (allWereAlive = allAlive(threads)) && (!interrupted)) {
218: try {
219: Thread.sleep(800L);
220: } catch (InterruptedException e) {
221: interrupted = true;
222: }
223: }
224:
225: System.out.println("...stopping testers");
226:
227: /*
228: ** Free up 2k of memory and garbage
229: ** collect. That should allow any
230: ** starved testers to stop themselves.
231: */
232: extraMemory = null;
233: System.gc();
234:
235: /*
236: ** Now stop everyone. First ask them to
237: ** willingly stop. By calling mtTester.stop()
238: ** we prevent the testers from picking up the
239: ** next task.
240: */
241: for (i = 0; i < testers.length; i++) {
242: testers[i].stop();
243: }
244:
245: /*
246: ** Sleep 180 seconds, or until everyone
247: ** is done.
248: */
249: System.out.println("...waiting for testers to complete");
250: for (i = 0; i < MAX_WAIT_FOR_COMPLETION; i++) {
251: try {
252: Thread.sleep((long) 1000);
253: } catch (InterruptedException e) {
254: System.out
255: .println("...Unexpected InterrupedException: "
256: + e);
257: }
258: if (allDead(threads)) {
259: break;
260: }
261: }
262:
263: if (i == MAX_WAIT_FOR_COMPLETION) {
264: log
265: .println("WARNING: testers didn't die willingly, so I'm going to kill 'em.");
266: log
267: .println("\tThis may result in connection resources that aren't cleaned up");
268: log
269: .println("\t(e.g. you may see problems in the final script run with deadlocks).");
270: }
271:
272: /*
273: ** Now stop everyone that hasn't already stopped.
274: */
275: for (i = 0; i < MAX_WAIT_FOR_COMPLETION
276: && (tg.isDestroyed() == false); i++) {
277:
278: // can't really stop - deprecated because 'unsafe'. interrupt.
279: tg.interrupt();
280: try {
281: Thread.sleep((long) 1000);
282: } catch (InterruptedException e) {
283: }
284:
285: try {
286: tg.destroy();
287: } catch (IllegalThreadStateException e) {
288: log
289: .println("...waiting for ThreadGroup.interrupt() to work its magic");
290: try {
291: Thread.sleep((long) 1000);
292: } catch (InterruptedException e2) {
293: }
294: continue;
295: }
296: break;
297: }
298:
299: if (interrupted == true) {
300: System.out.println("TEST CASE SUMMARY: run interrupted");
301: } else if (allWereAlive == false) {
302: System.out
303: .println("TEST CASE SUMMARY: abnormal termination due to error(s)"
304: + " -- see test log (./"
305: + testName
306: + "/"
307: + testName + ".log) for details ");
308: } else {
309: System.out.println("TEST CASE SUMMARY: normal termination");
310: if (i < MAX_WAIT_FOR_COMPLETION) {
311: try {
312: seqRunCases(suite.getFinalCases(), "last checks",
313: inputDir, outputDir);
314: } catch (ijFatalException e) {
315: System.out
316: .println("...error running final test cases");
317: }
318: } else {
319: System.out
320: .println("...timed out trying to kill all testers,\n"
321: + " skipping last scripts (if any). NOTE: the\n"
322: + " likely cause of the problem killing testers is\n"
323: + " probably not enough VM memory OR test cases that\n"
324: + " run for very long periods of time (so testers do not\n"
325: + " have a chance to notice stop() requests");
326: }
327: }
328:
329: return duration;
330: }
331:
332: /**
333: ** Search through the list of threads and see
334: ** if they are all alive.
335: */
336: public static boolean allAlive(Thread[] threads) {
337: int i;
338: for (i = 0; i < threads.length; i++) {
339: if (threads[i].isAlive() == false)
340: break;
341: }
342: return (i == threads.length);
343: }
344:
345: /**
346: ** Search through the list of threads and see
347: ** if they are all alive.
348: */
349: public static boolean allDead(Thread[] threads) {
350: int i;
351: for (i = 0; i < threads.length; i++) {
352: if (threads[i].isAlive() == true)
353: break;
354: }
355: return (i == threads.length);
356: }
357:
358: /**
359: ** Figure out the name of the log file and open
360: ** it
361: */
362: private static LocalizedOutput openFile(String dir, String fileName)
363: throws IOException {
364:
365: java.io.File file = new java.io.File(dir, fileName);
366:
367: return new LocalizedOutput(new FileOutputStream(file));
368: }
369:
370: /**
371: ** Sequentially run scripts
372: */
373: private static void seqRunCases(Vector cases, String descr,
374: String inputDir, String outputDir)
375: throws FileNotFoundException, IOException, ijFatalException {
376: LocalizedOutput out;
377: BufferedInputStream in;
378: mtTestCase testCase;
379:
380: if (cases == null) {
381: System.out.println("...no " + descr + " being performed");
382: return;
383: }
384:
385: Enumeration e = cases.elements();
386:
387: while (e.hasMoreElements()) {
388: testCase = (mtTestCase) e.nextElement();
389: String testName = testCase.getFile();
390: System.out.println("...running " + descr + " via "
391: + testName);
392: String logFileName = testName.substring(0, testName
393: .lastIndexOf('.'));
394: out = openFile(outputDir, logFileName + ".out");
395: in = testCase.initialize(inputDir);
396: testCase.runMe(log, out, in);
397: }
398: }
399:
400: /**
401: ** Given the command file, infer the test name.
402: ** Takes the portion of the file name between
403: ** the last '.' and the last '/'. e.g.
404: ** x/y/Name.suffix -> Name
405: **
406: */
407: private static String getTestName(String cmdFile) {
408: int slash, dotSpot;
409:
410: slash = cmdFile.lastIndexOf("/");
411: if (slash == -1) {
412: slash = 0;
413: }
414:
415: dotSpot = cmdFile.lastIndexOf(".");
416: if (dotSpot == -1) {
417: dotSpot = cmdFile.length();
418: }
419: return cmdFile.substring(slash, dotSpot);
420:
421: }
422: }
|