001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.junit;
038:
039: import junit.framework.*;
040:
041: import java.io.File;
042: import java.io.IOException;
043: import java.util.Enumeration;
044: import java.util.List;
045: import java.util.ArrayList;
046:
047: import edu.rice.cs.util.UnexpectedException;
048: import edu.rice.cs.util.StringOps;
049: import edu.rice.cs.util.classloader.ClassFileError;
050: import edu.rice.cs.util.swing.Utilities;
051: import edu.rice.cs.util.swing.ScrollableDialog;
052:
053: import java.lang.reflect.Modifier;
054:
055: import static edu.rice.cs.plt.debug.DebugUtil.debug;
056: import static edu.rice.cs.plt.debug.DebugUtil.error;
057:
058: /** Runs in the InterpreterJVM. Runs tests given a classname and formats the results into a (serializable) array of
059: * JUnitError that can be passed back to the MainJVM.
060: * @version $Id: JUnitTestManager.java 4255 2007-08-28 19:17:37Z mgricken $
061: */
062: public class JUnitTestManager {
063:
064: /** The interface to the master JVM via RMI. */
065: private final JUnitModelCallback _jmc;
066:
067: /** The current testRunner; initially null. Each test suite requires a new runner. */
068: private JUnitTestRunner _testRunner;
069:
070: /** The accumulated test suite; null if no test is pending. */
071: private TestSuite _suite = null;
072:
073: /** The accumulated list of names of TestCase classes; null if no test is pending. */
074: private List<String> _testClassNames = null;
075:
076: /** The list of files corresponding to testClassNames; null if no test is pending. */
077: private List<File> _testFiles = null;
078:
079: /** Standard constructor */
080: public JUnitTestManager(JUnitModelCallback jmc) {
081: _jmc = jmc;
082: }
083:
084: public JUnitTestRunner getTestRunner() {
085: return _testRunner;
086: }
087:
088: /** Find the test classes among the given classNames and accumulate them in
089: * TestSuite for junit. Returns null if a test suite is already pending.
090: * @param classNames the class names that are test class candidates
091: * @param files the files corresponding to classNames
092: */
093: public List<String> findTestClasses(final List<String> classNames,
094: final List<File> files) {
095: //debug.logStart("findTestClasses");
096: //debug.logValues(new String[]{"classNames", "files"}, classNames, files);
097:
098: if (_testClassNames != null && !_testClassNames.isEmpty())
099: throw new IllegalStateException(
100: "Test suite is still pending!");
101:
102: _testRunner = new JUnitTestRunner(_jmc);
103:
104: _testClassNames = new ArrayList<String>();
105: _testFiles = new ArrayList<File>();
106: _suite = new TestSuite();
107:
108: //new ScrollableDialog(null, "JUnitManager.findTestClasses invoked", "Candidate classes are = " + classNames, "files = " + files).show();
109:
110: int i = 0;
111: try {
112: for (i = 0; i < classNames.size(); i++) {
113: String cName = classNames.get(i);
114: //new ScrollableDialog(null, "Class to be checked in JUnitManager: " + cName, "", "").show();
115: try {
116: if (_isTestCase(cName)) {
117: //new ScrollableDialog(null, "Test class " + cName + " found!", "", "").show();
118: _testClassNames.add(cName);
119: _testFiles.add(files.get(i));
120: _suite.addTest(_testRunner.getTest(cName));
121: }
122: } catch (LinkageError e) {
123: //debug.log(e);
124: _jmc.classFileError(new ClassFileError(cName, files
125: .get(i).getCanonicalPath(), e));
126: }
127: }
128: } catch (IOException e) {
129: throw new UnexpectedException(e);
130: }
131: //new ScrollableDialog(null, "TestClassNames are: " + _testClassNames, "", "").show();
132:
133: //debug.logEnd("findTestClasses");
134: return _testClassNames;
135: }
136:
137: /** Runs the pending test suite set up by the preceding call to findTestClasses
138: * @return false if no test suite (even an empty one) has been set up
139: */
140: public/* synchronized */boolean runTestSuite() {
141:
142: if (_testClassNames == null || _testClassNames.isEmpty())
143: return false;
144:
145: // new ScrollableDialog(null, "runTestSuite() in SlaveJVM called", "", "").show();
146:
147: try {
148: TestResult result = _testRunner.doRun(_suite);
149:
150: JUnitError[] errors = new JUnitError[result.errorCount()
151: + result.failureCount()];
152:
153: Enumeration failures = result.failures();
154: Enumeration errEnum = result.errors();
155:
156: int i = 0;
157:
158: while (errEnum.hasMoreElements()) {
159: TestFailure tErr = (TestFailure) errEnum.nextElement();
160: errors[i] = _makeJUnitError(tErr, _testClassNames,
161: true, _testFiles);
162: i++;
163: }
164:
165: while (failures.hasMoreElements()) {
166: TestFailure tFail = (TestFailure) failures
167: .nextElement();
168: errors[i] = _makeJUnitError(tFail, _testClassNames,
169: false, _testFiles);
170: i++;
171: }
172: // new ScrollableDialog(null, "Slave JVM: testSuite ended with errors", "", Arrays.toString(errors)).show();
173:
174: _jmc.testSuiteEnded(errors);
175: } catch (Exception e) {
176: JUnitError[] errors = new JUnitError[1];
177: errors[0] = new JUnitError(null, -1, -1, e.getMessage(),
178: false, "", "", StringOps.getStackTrace(e));
179: _jmc.testSuiteEnded(errors);
180: // new ScrollableDialog(null, "Slave JVM: testSuite ended with errors", "", Arrays.toString(errors)).show();
181:
182: } finally {
183: _suite = null;
184: _testClassNames = null;
185: _testFiles = null;
186: }
187: return true;
188: }
189:
190: /** Determines if the given class is a junit Test.
191: * @param c the class to check
192: * @return true iff the given class is an instance of junit.framework.Test
193: */
194: private boolean _isJUnitTest(Class c) {
195: boolean result = Test.class.isAssignableFrom(c)
196: && !Modifier.isAbstract(c.getModifiers())
197: && !Modifier.isInterface(c.getModifiers());
198: //debug.logValues(new String[]{"c", "isJUnitTest(c)"}, c, result);
199: return result;
200: }
201:
202: /** Checks whether the given file name corresponds to a valid JUnit Test. */
203: private boolean _isTestCase(String className) {
204: try {
205: return _isJUnitTest(_testRunner.getLoader().load(className));
206: } catch (ClassNotFoundException cnfe) {
207: error.log(cnfe);
208: return false;
209: }
210: }
211:
212: /** Constructs a new JUnitError from a TestFailure
213: * @param failure A given TestFailure
214: * @param classNames The classes that were used for this test suite
215: * @param isError The passed TestFailure may signify either an error or a failure
216: * @param files The files that were used for this test suite
217: * @return JUnitError
218: */
219: private JUnitError _makeJUnitError(TestFailure failure,
220: List<String> classNames, boolean isError, List<File> files) {
221:
222: Test failedTest = failure.failedTest();
223: String testName;
224: if (failedTest instanceof TestCase)
225: testName = ((TestCase) failedTest).getName();
226: else
227: testName = failedTest.getClass().getName();
228:
229: String testString = failure.toString();
230: int firstIndex = testString.indexOf('(') + 1;
231: int secondIndex = testString.indexOf(')');
232:
233: /** junit can return a string in two different formats; we parse both formats, and then decide which one to use. */
234:
235: String className;
236: String className1 = testString.substring(firstIndex,
237: secondIndex);
238: String className2 = testString.substring(0, firstIndex - 1);
239: if (firstIndex == secondIndex)
240: className = className2;
241: else
242: className = className1;
243:
244: String classNameAndTest = className + "." + testName;
245: String stackTrace = StringOps.getStackTrace(failure
246: .thrownException());
247:
248: /** If the classname is not in the stacktrace, then the test that failed was inherited from a superclass. let's look
249: * for the classname.
250: */
251: if (stackTrace.indexOf(className) == -1) {
252: /* get the stack trace of the junit error */
253: String trace = failure.trace();
254: /* knock off the first line of the stack trace.
255: * now the string will look like
256: * at my.package.class(file.java:line)
257: * at other.package.class(anotherfile.java:line)
258: * etc...
259: */
260: trace = trace.substring(trace.indexOf('\n') + 1);
261: while (trace.indexOf("junit.framework.Assert") != -1
262: && trace.indexOf("junit.framework.Assert") < trace
263: .indexOf("(")) {
264: /* the format of the trace will have "at junit.framework.Assert..."
265: * on each line until the line of the actual source file.
266: * if the exception was thrown from the test case (so the test failed
267: * without going through assert), then the source file will be on
268: * the first line of the stack trace
269: */
270: trace = trace.substring(trace.indexOf('\n') + 1);
271: }
272: trace = trace.substring(trace.indexOf('(') + 1);
273: trace = trace.substring(0, trace.indexOf(')'));
274: className = trace.substring(0, trace.indexOf(':'));
275: className = trace.substring(0, trace.lastIndexOf('.'));
276: classNameAndTest = className + "." + testName;
277: }
278:
279: int lineNum = _lineNumber(stackTrace, classNameAndTest);
280:
281: // if (lineNum > -1) _errorsWithPos++;
282:
283: String exception = (isError) ? failure.thrownException()
284: .toString() : failure.thrownException().getMessage();
285: boolean isFailure = (failure.thrownException() instanceof AssertionFailedError)
286: && !classNameAndTest
287: .equals("junit.framework.TestSuite$1.warning");
288:
289: // for dubugging
290: // try{
291: // File temp = File.createTempFile("asdf", "java", new File("/home/awulf"));
292: // FileWriter writer = new FileWriter(temp);
293: // writer.write("testString: " + testString + "\n");
294: // writer.write("old className: " + className1 + "\n");
295: // writer.write("new className: " + className2 + "\n");
296: // writer.write("file: " + file + "\n");
297: // writer.write("lineNum: " + lineNum + "\n");
298: // writer.write("exception: " + exception + "\n");
299: // writer.write("!isFailure: " + !isFailure + "\n");
300: // writer.write("testName: " + testName + "\n");
301: // writer.write("className: " + className + "\n");
302: // writer.write("stackTrace: " + stackTrace + "\n");
303: // writer.close();
304: // } catch(IOException e) {
305: //
306: // }
307:
308: int indexOfClass = classNames.indexOf(className);
309: File file;
310: if (indexOfClass != -1)
311: file = files.get(indexOfClass);
312: else
313: file = _jmc.getFileForClassName(className);
314:
315: // if testClass contains no
316:
317: // a test didn't fail, we couldn't even open the test.
318: if (file == null) {
319: return new JUnitError(new File("nofile"), 0, 0, exception,
320: !isFailure, testName, className, stackTrace);
321: }
322:
323: // The code augmentation for elementary and intermediate level files causes the error to be highlighted on
324: // the wrong line. The following code adjusts for this discrepancy.
325: String name = file.getName();
326: int adjLineNum;
327: if (name.endsWith(".dj0") || name.endsWith(".dj0"))
328: adjLineNum = lineNum - 1;
329: else
330: adjLineNum = lineNum;
331:
332: return new JUnitError(file, adjLineNum, 0, exception,
333: !isFailure, testName, className, stackTrace);
334: }
335:
336: /** Parses the line number out of the stack trace in the given class name. */
337: private int _lineNumber(String sw, String classname) {
338: int lineNum;
339: int idxClassname = sw.indexOf(classname);
340: if (idxClassname == -1)
341: return -1;
342:
343: String theLine = sw.substring(idxClassname, sw.length());
344:
345: theLine = theLine.substring(theLine.indexOf(classname), theLine
346: .length());
347: theLine = theLine.substring(theLine.indexOf("(") + 1, theLine
348: .length());
349: theLine = theLine.substring(0, theLine.indexOf(")"));
350:
351: try {
352: int i = theLine.indexOf(":") + 1;
353: lineNum = Integer.parseInt(theLine.substring(i, theLine
354: .length())) - 1;
355: } catch (NumberFormatException e) {
356: throw new UnexpectedException(e);
357: }
358:
359: return lineNum;
360: }
361: }
|