Source code: com/meterware/servletunit/JUnitServlet.java
1 package com.meterware.servletunit;
2 /********************************************************************************************************************
3 * $Id: JUnitServlet.java,v 1.7 2003/07/22 01:49:08 russgold Exp $
4 *
5 * Copyright (c) 2001-2003, Russell Gold
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
8 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
10 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in all copies or substantial portions
13 * of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
16 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
18 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19 * DEALINGS IN THE SOFTWARE.
20 *
21 *******************************************************************************************************************/
22 import java.io.IOException;
23 import java.io.PrintWriter;
24 import java.util.Enumeration;
25
26 import javax.servlet.http.HttpServlet;
27 import javax.servlet.http.HttpServletRequest;
28 import javax.servlet.http.HttpServletResponse;
29 import javax.servlet.ServletException;
30
31 import junit.runner.BaseTestRunner;
32 import junit.runner.TestSuiteLoader;
33 import junit.runner.StandardTestSuiteLoader;
34 import junit.framework.Test;
35 import junit.framework.AssertionFailedError;
36 import junit.framework.TestResult;
37 import junit.framework.TestFailure;
38
39
40 /**
41 * A servlet which can run unit tests inside a servlet context. It may be extended to provide InvocationContext-access
42 * to such tests if a container-specific implementation of InvocationContextFactory is provided.
43 * Combined with ServletTestCase, this would permit
44 * in-container tests of servlets in a fashion similar to that supported by ServletUnit.
45 *
46 * @author <a href="mailto:russgold@httpunit.org">Russell Gold</a>
47 **/
48 public class JUnitServlet extends HttpServlet {
49
50 public JUnitServlet() {
51 }
52
53
54 protected JUnitServlet( InvocationContextFactory factory ) {
55 _factory = factory;
56 }
57
58
59 protected void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException, IOException {
60 ResultsFormatter formatter = getResultsFormatter( request.getParameter( "format" ) );
61 response.setContentType( formatter.getContentType() );
62 final String testName = request.getParameter( "test" );
63 if (testName == null || testName.length() == 0) {
64 reportCannotRunTest( response.getWriter(), "No test class specified" );
65 } else {
66 ServletTestRunner runner = new ServletTestRunner( response.getWriter(), formatter );
67 runner.runTestSuite( testName );
68 }
69 response.getWriter().close();
70 }
71
72
73 private ResultsFormatter getResultsFormatter( String formatterName ) {
74 if ("text".equalsIgnoreCase( formatterName )) {
75 return new TextResultsFormatter();
76 } else if ("xml".equalsIgnoreCase( formatterName )) {
77 return new XMLResultsFormatter();
78 } else {
79 return new HTMLResultsFormatter();
80 }
81 }
82
83
84 private InvocationContextFactory _factory;
85
86
87 private void reportCannotRunTest( PrintWriter writer, final String errorMessage ) {
88 writer.print( "<html><head><title>Cannot run test</title></head><body>" + errorMessage + "</body></html>" );
89 }
90
91
92 class ServletTestRunner extends BaseTestRunner {
93 private PrintWriter _writer;
94 private ResultsFormatter _formatter;
95
96
97 public ServletTestRunner( PrintWriter writer, ResultsFormatter formatter ) {
98 ServletTestCase.setInvocationContextFactory( _factory );
99 _writer = writer;
100 _formatter = formatter;
101 }
102
103
104 void runTestSuite( String testClassName ) {
105 Test suite = getTest( testClassName );
106
107 if (suite != null) {
108 TestResult testResult = new TestResult();
109 testResult.addListener( this );
110 long startTime= System.currentTimeMillis();
111 suite.run( testResult );
112 long endTime= System.currentTimeMillis();
113 _formatter.displayResults( _writer, testClassName, elapsedTimeAsString( endTime-startTime ), testResult );
114 }
115 }
116
117
118 public void addError( Test test, Throwable throwable ) {
119 }
120
121
122 public void addFailure( Test test, AssertionFailedError error ) {
123 }
124
125
126 public void endTest( Test test ) {
127 }
128
129
130 protected void runFailed( String s ) {
131 reportCannotRunTest( _writer, s );
132 }
133
134
135 public void startTest( Test test ) {
136 }
137
138
139 public void testStarted( String s ) {
140 }
141
142
143 public void testEnded( String s ) {
144 }
145
146
147 public void testFailed( int i, Test test, Throwable throwable ) {
148 }
149
150
151 /**
152 * Always use the StandardTestSuiteLoader. Overridden from
153 * BaseTestRunner.
154 */
155 public TestSuiteLoader getLoader() {
156 return new StandardTestSuiteLoader();
157 }
158
159 }
160
161
162 static abstract class ResultsFormatter {
163
164 private static final char LF = 10;
165 private static final char CR = 13;
166
167
168 abstract String getContentType();
169
170
171 void displayResults( PrintWriter writer, String testClassName, String elapsedTimeString, TestResult testResult ) {
172 displayHeader( writer, testClassName, testResult, elapsedTimeString );
173 displayResults( writer, testResult );
174 displayFooter( writer );
175 }
176
177
178 protected abstract void displayHeader( PrintWriter writer, String testClassName, TestResult testResult, String elapsedTimeString );
179
180
181 protected abstract void displayResults( PrintWriter writer, TestResult testResult );
182
183
184 protected abstract void displayFooter( PrintWriter writer );
185
186
187 protected String sgmlEscape( String s ) {
188 if (s == null) return "NULL";
189 StringBuffer result = new StringBuffer( s.length() );
190 char[] chars = s.toCharArray();
191 for (int i = 0; i < chars.length; i++) {
192 switch( chars[i] ) {
193 case '&':
194 result.append( "&" );
195 break;
196 case '<':
197 result.append( "<" );
198 break;
199 case '>':
200 result.append( ">" );
201 break;
202 case LF:
203 if (i > 0 && chars[i-1] == CR) {
204 result.append( chars[i] );
205 break;
206 }
207 case CR:
208 result.append( getLineBreak() );
209 default:
210 result.append( chars[i] );
211 }
212 }
213 return result.toString();
214 }
215
216
217 protected String getLineBreak() {
218 return "<br>";
219 }
220 }
221
222
223 abstract static class DisplayedResultsFormatter extends ResultsFormatter {
224
225 protected void displayHeader( PrintWriter writer, String testClassName, TestResult testResult, String elapsedTimeString ) {
226 displayHeader( writer, testClassName, getFormatted( testResult.runCount(), "test" ),
227 elapsedTimeString, testResult.wasSuccessful() ? "OK" : "Problems Occurred" );
228 }
229
230 protected void displayResults( PrintWriter writer, TestResult testResult ) {
231 if (!testResult.wasSuccessful()) {
232 displayProblems( writer, "failure", testResult.failureCount(), testResult.failures() );
233 displayProblems( writer, "error", testResult.errorCount(), testResult.errors() );
234 }
235 }
236
237
238 protected abstract void displayHeader( PrintWriter writer, String testClassName, String testCountText, String elapsedTimeString, String resultString );
239
240
241 protected abstract void displayProblemTitle( PrintWriter writer, String title );
242
243
244 protected abstract void displayProblemDetailHeader( PrintWriter writer, int i, String testName );
245
246
247 protected abstract void displayProblemDetailFooter( PrintWriter writer );
248
249
250 protected abstract void displayProblemDetail( PrintWriter writer, String message );
251
252
253 private void displayProblems( PrintWriter writer, String kind, int count, Enumeration enumeration ) {
254 if (count != 0) {
255 displayProblemTitle( writer, getFormatted( count, kind ) );
256 Enumeration e = enumeration;
257 for (int i = 1; e.hasMoreElements(); i++) {
258 TestFailure failure = (TestFailure) e.nextElement();
259 displayProblemDetailHeader( writer, i, failure.failedTest().toString() );
260 if (failure.thrownException() instanceof AssertionFailedError) {
261 displayProblemDetail( writer, failure.thrownException().getMessage() );
262 } else {
263 displayProblemDetail( writer, BaseTestRunner.getFilteredTrace( failure.thrownException() ) );
264 }
265 displayProblemDetailFooter( writer );
266 }
267 }
268 }
269
270
271 private String getFormatted( int count, String name ) {
272 return count + " " + name + (count == 1 ? "" : "s");
273 }
274
275
276 }
277
278
279 static class TextResultsFormatter extends DisplayedResultsFormatter {
280
281 String getContentType() {
282 return "text/plain";
283 }
284
285
286 protected void displayHeader( PrintWriter writer, String testClassName, String testCountText,
287 String elapsedTimeString, String resultString ) {
288 writer.println( testClassName + " (" + testCountText + "): " + resultString );
289 }
290
291
292 protected void displayFooter( PrintWriter writer ) {
293 }
294
295
296 protected void displayProblemTitle( PrintWriter writer, String title ) {
297 writer.println();
298 writer.println( title + ':' );
299 }
300
301
302 protected void displayProblemDetailHeader( PrintWriter writer, int i, String testName ) {
303 writer.println( i + ". " + testName + ":" );
304 }
305
306
307 protected void displayProblemDetailFooter( PrintWriter writer ) {
308 writer.println();
309 }
310
311
312 protected void displayProblemDetail( PrintWriter writer, String message ) {
313 writer.println( message );
314 }
315 }
316
317
318 static class HTMLResultsFormatter extends DisplayedResultsFormatter {
319
320 String getContentType() {
321 return "text/html";
322 }
323
324
325 protected void displayHeader( PrintWriter writer, String testClassName, String testCountText,
326 String elapsedTimeString, String resultString ) {
327 writer.println( "<html><head><title>Test Suite: " + testClassName + "</title>" );
328 writer.println( "<style type='text/css'>" );
329 writer.println( "<!--" );
330 writer.println( " td.detail { font-size:smaller; vertical-align: top }" );
331 writer.println( " -->" );
332 writer.println( "</style></head><body>" );
333 writer.println( "<table id='results' border='1'><tr>" );
334 writer.println( "<td>" + testCountText + "</td>" );
335 writer.println( "<td>Time: " + elapsedTimeString + "</td>" );
336 writer.println( "<td>" + resultString + "</td></tr>" );
337 }
338
339
340 protected void displayFooter( PrintWriter writer ) {
341 writer.println( "</table>" );
342 writer.println( "</body></html>" );
343 }
344
345
346 protected void displayProblemTitle( PrintWriter writer, String title ) {
347 writer.println( "<tr><td colspan=3>" + title + "</td></tr>" );
348 }
349
350
351 protected void displayProblemDetailHeader( PrintWriter writer, int i, String testName ) {
352 writer.println( "<tr><td class='detail' align='right'>" + i + "</td>" );
353 writer.println( "<td class='detail'>" + testName + "</td><td class='detail'>" );
354 }
355
356
357 protected void displayProblemDetailFooter( PrintWriter writer ) {
358 writer.println( "</td></tr>" );
359 }
360
361
362 protected void displayProblemDetail( PrintWriter writer, String message ) {
363 writer.println( sgmlEscape( message ) );
364 }
365
366 }
367
368
369
370 static class XMLResultsFormatter extends ResultsFormatter {
371
372 String getContentType() {
373 return "text/xml;charset=UTF-8";
374 }
375
376
377 protected void displayHeader( PrintWriter writer, String testClassName, TestResult testResult, String elapsedTimeString ) {
378 writer.println( "<?xml version='1.0' encoding='UTF-8' ?>\n" +
379 "<testsuite name=" + asAttribute( testClassName ) +
380 " tests=" + asAttribute( testResult.runCount() ) +
381 " failures=" + asAttribute( testResult.failureCount() ) +
382 " errors=" + asAttribute( testResult.errorCount() ) +
383 " time=" + asAttribute( elapsedTimeString ) + ">" );
384 }
385
386
387 private String asAttribute( int value ) {
388 return '"' + Integer.toString( value ) + '"';
389 }
390
391
392 private String asAttribute( String value ) {
393 return '"' + sgmlEscape( value ) + '"';
394 }
395
396
397 protected void displayFooter( PrintWriter writer ) {
398 writer.println( "</testsuite>" );
399 }
400
401
402 protected void displayResults( PrintWriter writer, TestResult testResult ) {
403 displayResults( writer, "failure", testResult.failures() );
404 displayResults( writer, "error", testResult.errors() );
405 }
406
407
408 private void displayResults( PrintWriter writer, String failureNodeName, Enumeration resultsEnumeration ) {
409 for (Enumeration e = resultsEnumeration; e.hasMoreElements();) {
410 TestFailure failure = (TestFailure) e.nextElement();
411 writer.println( " <testcase name=" + asAttribute( failure.failedTest().toString() ) + ">" );
412 writer.print( " <" + failureNodeName + " type=" + asAttribute( failure.thrownException().getClass().getName() ) +
413 " message=" + asAttribute( failure.exceptionMessage() ) );
414 if (!displayException( failure )) {
415 writer.println( "/>" );
416 } else {
417 writer.println( ">" );
418 writer.print( sgmlEscape( BaseTestRunner.getFilteredTrace( failure.thrownException() ) ) );
419 writer.println( " </" + failureNodeName + ">" );
420 }
421 writer.println( " </testcase>" );
422 }
423 }
424
425
426 private boolean displayException( TestFailure failure ) {
427 return true;
428 }
429
430
431 protected String getLineBreak() {
432 return "";
433 }
434 }
435
436
437 }