View Javadoc
1   package eu.simuline.testhelpers;
2   
3   import eu.simuline.util.images.GifResource;
4   
5   import eu.simuline.junit.AssumptionFailure;
6   
7   import junit.framework.AssertionFailedError;
8   
9   import java.awt.Color;
10  
11  import javax.swing.ImageIcon;
12  
13  import org.junit.runner.notification.RunListener; // for javadoc only 
14  import org.junit.runner.notification.Failure; // for javadoc only 
15  
16  import org.junit.AssumptionViolatedException;
17  
18  
19  /**
20   * Represents the phases in the life-cycle of a {@link TestCase} 
21   * from being {@link #Scheduled} to {@link #Ignored} 
22   * or via {@link #Started} to finished 
23   * which means either {@link #Invalidated}, {@link #Success}, 
24   * {@link #Failure} or even {@link #Error}. 
25   * <p>
26   * The phase transitions are triggered by the events 
27   * notified by a {@link RunListener} as depicted below. 
28  
29   * <pre>
30   *                              |{@link RunListener#testRunStarted(Description)}
31   *                              |
32   *                   {@link #Scheduled}
33   *                             /|
34   *                            / |
35   * {@link RunListener
36   *#testIgnored(Description)}/   |{@link RunListener#testStarted(Description)}
37   *                         /    |
38   *                        /     |
39   *          {@link #Ignored}    |
40   *                              |
41   *                    {@link #Started}
42   *                             /|\
43   * {@link RunListener
44   *#testFinished(Description)}/  |{@link RunListener
45   *                                        #testAssumptionFailure(Failure)}
46   *                         /    |    \
47   *                        /     |     \ 
48   *        _______________/      |      \__________
49   *       /                      |                 \ 
50   *      / {@link RunListener#testFailure(Failure)} \ 
51   *     /                        /\                  \
52   *    /                        /  \                  \ 
53   *   /                        /    \                  \
54   * {@link #Success} {@link #Failure} {@link #Error} {@link #Invalidated}. 
55   * </pre>
56   *
57   * Accordingly, 
58   * there are methods <code>setXXX</code> for transition to the next state: 
59   * {@link #setScheduled()}, {@link #setStarted()} prior to testing, 
60   * {@link #setIgnored()} if not to be tested and 
61   * {@link #setFinished()}, {@link #setAssumptionFailure()} and 
62   * {@link #setFailure(Throwable)} 
63   * after the test finished. 
64   * All these methods prevent invalid phase transitions 
65   * throwin an {@link IllegalStateException}. 
66   * 
67   * <p>
68   * For each of these phases, there are methods to represent the quality 
69   * or further aspects of a testcase graphically: Each Quality defines 
70   * <ul>
71   * <li>a separate icon given by {@link #getIcon()}, 
72   * <li>a status message given by {@link #getMessage()} 
73   * <li>a color {@link #getColor()} influencing the color of the progress bar 
74   * <li>a method {@link #setTime(long)} to compute the time 
75   * required for a test run.
76   * </ul>
77   *
78   * Finally, there are further methods to ask for certain properties 
79   * as {@link #isNeutral()} and {@link #hasFailure()}. 
80   *
81   * Created: Wed Jun 12 16:41:14 2006
82   *
83   * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
84   * @version 1.0
85   */
86  enum Quality {
87  
88      /* -------------------------------------------------------------------- *
89       * constant constructors.                                               *
90       * -------------------------------------------------------------------- */
91  
92  
93      /**
94       * The testcase is scheduled for execution 
95       * but execution has not yet been started 
96       * nor has it been decided to ignore, i.e. not to execute the testcase. 
97       *
98       * @see #Started
99       * @see #Ignored
100      */
101     Scheduled(0) {
102 	ImageIcon getIcon() {
103 	    return GifResource.getIcon(eu.simuline.sun.gtk.File.class);
104 	}
105 	Quality setStarted() {
106 	    return Started;
107 	}
108 	Quality setFinished() {
109 	    throw new IllegalStateException
110 		("Found testcase finished before started. ");
111 	}
112 	Quality setIgnored() {
113 	    return Ignored;
114 	}
115 	String getMessage() {
116 	    return "Scheduled";
117 	}
118 	long setTime(long time) {
119 	    return TestCase.TIME_SCHEDULED;
120 	}
121     },
122 
123     /**
124      * The execution of the testcase started but did not finish 
125      * in any way. 
126      * Thus it is neither clear whether the execution succeeds 
127      * nor the outcoming of the test. 
128      */
129     Started(0) {
130 	ImageIcon getIcon() {
131 	    return GifResource.getIcon(eu.simuline.junit.New.class);
132 	}
133 	Quality setFinished() {
134 	    return Success;
135 	}
136 	Quality setIgnored() {
137 	    throw new IllegalStateException
138 		("Found testcase to be ignored while running. ");
139 	}
140 	Quality setScheduled() {
141 	    throw new IllegalStateException
142 		("Found testcase scheduled before started. ");
143 	}
144 	Quality setStarted() {
145 	    throw new IllegalStateException
146 		("Found testcase started while running. ");
147 	}
148 	String getMessage() {
149 	    return "started";
150 	}
151 	long setTime(long time) {
152 	    return System.currentTimeMillis();
153 	}
154 	Quality setAssumptionFailure() {
155 	    return Invalidated;
156 	}
157 	// **** should be based on AssertionFailedError only, 
158 	// but junit does not admit this. 
159  	Quality setFailure(Throwable thrw) {
160 	    return thrw instanceof AssertionFailedError 
161 		|| thrw instanceof AssertionError ? Failure : Error;
162 	}
163     },
164     /**
165      * The execution of the testcase finished and the test succeeded (passed): 
166      * All assertions hold and no throwable has been thrown. 
167      */
168     Success(0) {
169 	ImageIcon getIcon() {
170 	    return GifResource.getIcon(eu.simuline.junit.Ok.class);
171 	}
172 	Quality setFinished() {
173 	    throw new IllegalStateException
174 		("Found testcase successful before finished. ");
175 	}
176 	String getMessage() {
177 	    return "succeeded";
178 	}
179     },
180     /**
181      * The execution of the testcase ran onto an hurt assumption 
182      * before maybe running into further exceptions or errors 
183      * and is thus invalidated. 
184      */
185     Invalidated(1) {
186 	ImageIcon getIcon() {
187 	    return GifResource.getIcon(AssumptionFailure.class);
188 	}
189 	String getMessage() {
190 	    return "invalidated by failed assumption";
191 	}
192     },
193     /**
194      * The testcase was ignored, i.e. was scheduled for execution 
195      * but then decided not to start execution. 
196      * In particular, nothing can be said about the course of the test run. 
197      */
198     Ignored(1) {
199 	ImageIcon getIcon() {
200 	    return GifResource.getIcon(eu.simuline.junit.Ignored.class);
201 	}
202 	long setTime(long time) {
203 	    return 0;
204 	}
205 	Quality setFinished() {
206 	    throw new IllegalStateException
207 		("Found testcase finished and ignored. ");
208 	}
209 	String getMessage() {
210 	    return "was ignored";
211 	}
212     }, 
213     /**
214      * The execution of the testcase finished gracefully 
215      * but did not succeed: 
216      * At least one assertion is hurt, 
217      * indicated by an {@link AssertionFailedError}. 
218      * This excludes further throwables. 
219      */
220     Failure(2) {
221 	ImageIcon getIcon() {
222 	    return GifResource.getIcon(eu.simuline.junit.Failure.class);
223 	}
224 	String getMessage() {
225 	    return "failed";
226 	}
227     }, 
228     /**
229      * The execution of the testcase failed 
230      * indicated by finishing with exception or error 
231      * other than {@link AssertionFailedError}. 
232      * Thus there is no valid test result. 
233      */
234     Error(2) {
235 	ImageIcon getIcon() {
236 	    return GifResource.getIcon(eu.simuline.junit.Error.class);
237 	}
238 	String getMessage() {
239 	    return "had an error";
240 	}
241     };
242 
243 
244     /* -------------------------------------------------------------------- *
245      * fields.                                                              *
246      * -------------------------------------------------------------------- */
247 
248     /**
249      * Encoding how good the result of the test is: 
250      * <ul>
251      * <li><code>2</code> if something went wrong: 
252      *     {@link #Error} or {@link #Failure}.  
253      * <li><code>1</code> if something was ignored 
254      *     {@link #Ignored} or {@link #Invalidated}. 
255      * <li><code>0</code> else: 
256      *     {@link #Scheduled}, {@link #Started} or {@link #Success}. 
257      * </ul>
258      * This is used to define the color of the progress bar. 
259      *
260      * @see #max(Quality)
261      */
262     private int level;
263 
264     /* -------------------------------------------------------------------- *
265      * constructors.                                                        *
266      * -------------------------------------------------------------------- */
267 
268     /**
269      * Creates another Quality with the given level {@link #level}. 
270      */
271     Quality(int level) {
272 	this.level = level;
273     }
274 
275  
276     /* -------------------------------------------------------------------- *
277      * class constants.                                                     *
278      * -------------------------------------------------------------------- */
279 
280     /**
281      * Represents a failed testcase. 
282      */
283     private static final Color COLOR_FAIL    = Color.red;
284 
285     /**
286      * Represents a testcase which did neither failed. 
287      */
288     private static final Color COLOR_OK      = Color.green;
289 
290     /**
291      * Represents an ignored testcase. 
292      */
293     private static final Color COLOR_IGNORED = Color.yellow;
294 
295     /* -------------------------------------------------------------------- *
296      * methods for phase transitions.                                       *
297      * -------------------------------------------------------------------- */
298 
299     /**
300      * Returns the next phase 
301      * when {@link RunListener#testRunStarted(Description)} is invoked. 
302      * 
303      * @return
304      *    {@link #Scheduled} except this is {@link #Started}. 
305      *    Even for {@link #Scheduled} itself. 
306      * @throws IllegalStateException 
307      *    for {@link #Started}. 
308      *    **** it is a decision to disallow rescheduling a running testcase. 
309      */
310     Quality setScheduled() {
311 	return Scheduled;
312     }
313 
314     /**
315      * Returns the next phase 
316      * when {@link RunListener#testStarted(Description)} is invoked. 
317      * 
318      * @return
319      *    {@link #Started} if this is {@link #Scheduled}. 
320      * @throws IllegalStateException 
321      *    except if this is {@link #Scheduled}
322      */
323     Quality setStarted() {
324 	throw new IllegalStateException
325 	    ("Found testcase started but not scheduled. ");
326     }
327 
328     /**
329      * Returns the next phase 
330      * when {@link RunListener#testIgnored(Description)} is invoked. 
331      *
332      * @return
333      *    {@link #Ignored} if this is {@link #Scheduled}. 
334      * @throws IllegalStateException 
335      *    except for {@link #Scheduled}. 
336      */
337     Quality setIgnored() {
338 	throw new IllegalStateException
339 	    ("Found testcase ignored but not scheduled. ");
340     }
341 
342     /**
343      * Returns the next phase 
344      * when {@link RunListener#testFinished(Description)} is invoked. 
345      *
346      * @return
347      *    <ul>
348      *    <li><code>this</code> 
349      *       for {@link #Failure},  {@link #Invalidated} and {@link #Error}  
350      *    <li>{@link #Success} for {@link #Started}. 
351      *    </ul>
352      * @throws IllegalStateException 
353      *    for {@link #Scheduled}, {@link #Success} and {@link #Ignored}. 
354      */
355     Quality setFinished() {
356 	return this;
357     }
358 
359     /**
360      * Returns  the next phase 
361      * when {@link RunListener#testAssumptionFailure(Failure)} is invoked. 
362      *
363      * @return
364      *    {@link #Invalidated} for {@link #Started}. 
365      * @throws IllegalStateException 
366      *    for all but {@link #Started}. 
367      */
368     Quality setAssumptionFailure() {
369 	throw new IllegalStateException
370 	    ("Found testcase with assumtion failure which is not started. ");
371     }
372 
373     /**
374      * Returns  the next phase 
375      * when {@link RunListener#testFailure(Failure)} is invoked. 
376      *
377      * @param thrw
378      *    a throwable which determines 
379      *    whether the returned phase indicates a failure or an error. 
380      * @return
381      *    {@link #Failure} or {@link #Error} for {@link #Started} 
382      *    depending on whether <code>thrw</code> 
383      *    is an {@link AssertionError} 
384      *    (should be {@link AssertionFailedError}). 
385      * @throws IllegalStateException 
386      *    for all but {@link #Started}. 
387      *    If this is {@link #Started} 
388      *    but <code>failure</code> is an {@link AssumptionViolatedException}. 
389      */
390     Quality setFailure(Throwable thrw) {
391 	throw new IllegalStateException
392 	    ("Found testcase with failure which is not started. ");
393     }
394 
395     /* -------------------------------------------------------------------- *
396      * methods for phase representation.                                    *
397      * -------------------------------------------------------------------- */
398 
399      /**
400      * Returns an icon representing this phase on the GUI. 
401      *
402      * @return
403      *    an icon representing this phase on the GUI. 
404      */
405     abstract ImageIcon getIcon();
406 
407     /**
408      * Returns a status message which describes this phase. 
409      *
410      * @return
411      *    a status message which describes this phase. 
412      */
413     abstract String getMessage();
414 
415     /**
416      * Determines a Quality with the maximum level 
417      * of <code>this</code> and <code>other</code>. 
418      * In conjunction with {@link #getColor()}, 
419      * this is used to determine the color of the progress bar in a JUnit GUI 
420      *
421      * @return
422      *   the Quality with the maximum level 
423      *   of <code>this</code> and <code>other</code>. 
424      * @see GUIRunner.TestProgressBar#noteReportResult(TestCase)
425      */
426     Quality max(Quality other) {
427 	return (this.level > other.level) ? this : other;
428     }
429 
430     /**
431      * Returns the color associated with this phase: 
432      * If this phase represents an irregular ending testcase, 
433      * {@link #COLOR_FAIL} is returned. 
434      * Else if this phase represents an ignored testcase, 
435      * {@link #COLOR_IGNORED} is returned. 
436      * Else {@link #COLOR_OK} is returned. 
437      *
438      * @return
439      *    the color associated with this phase. 
440      * @see #max(Quality)
441      */
442     Color getColor() {
443 	switch (this.level) {
444 	case 0:
445 	    return COLOR_OK;
446 	case 1:
447 	    return COLOR_IGNORED;
448 	case 2:
449 	    return COLOR_FAIL;
450 	default:
451 	    throw new IllegalStateException
452 		("Found undefined level " + this.level + ". ");
453 	}
454     }
455 
456     /**
457      * Returns the difference of the current time in milliseconds. 
458      *
459      * @param time
460      *    some time in milliseconds. 
461      *    The details depend on this: 
462      *    <ul>
463      *    <li>{@link #Scheduled}, {@link #Ignored} and {@link #Started} 
464      *        ignore that parameter. 
465      *    <li>{@link #Failure},  {@link #Invalidated}, {@link #Error} and 
466      *        {@link #Success} interprete <code>time</code> 
467      *        as the start time of the underlying testcase. 
468      *    </ul>
469      * @return
470      *    <ul>
471      *    <li><code>-1</code> for {@link #Scheduled}. 
472      *    <li><code>-0</code> for {@link #Ignored}. 
473      *    <li>The current time {@link System#currentTimeMillis()} 
474      *        for {@link #Started}. 
475      *    <li>the span of time the underlying testcase took 
476      *        from start to finish 
477      *    </ul>
478      */
479     long setTime(long time) {
480 	return System.currentTimeMillis() - time;
481     }
482 
483 
484 
485     // /**
486     //  * Whether the run of the underlying testcase stopped irregularly. 
487     //  * This is the case only for {@link #Failure} and {@link #Error}. 
488     //  */
489     // boolean isIrregular() {
490     // 	return this.level == 2;
491     // }
492 
493     // /**
494     //  * Returns whether the underlying testcase is ignored. 
495     //  * This is <code>false</code> 
496     //  * except for {@link #Ignored} and {@link #Invalidated}. 
497     //  */
498     // boolean isIgnored() {
499     // 	return this.level == 1;
500     // }
501 
502    /**
503      * Returns whether the underlying testcase is 
504      * neither finished unsuccessfully nor ignored. 
505      * This is <code>false</code> 
506      * except for {@link #Scheduled}, {@link #Started} and {@link #Success}. 
507      * Neutral qualities do not occur in the statistics 
508      * given by {@link GUIRunner.StatisticsTestState}. 
509      *
510      * @return
511      *    whether the underlying testcase is 
512      *    neither finished unsuccessfully nor ignored. 
513      */
514     boolean isNeutral() {
515 	return this.level == 0;
516     }
517 
518     /**
519      * Returns whether the given state is tied to a throwable. 
520      *
521      * @return
522      *    <code>true</code> iff this is 
523      *    {@link #Invalidated}, {@link #Failure} or {@link #Error}. 
524      */
525     boolean hasFailure() {
526 	switch (this.level) {
527 	case 0:
528 	    return false;
529 	case 1:
530 	    return this == Invalidated;
531 	case 2:
532 	    return true;
533 	default:
534 	    throw new IllegalStateException
535 		("Found undefined level " + this.level + ". ");
536 	}
537     }
538 
539 } // enum Quality 
540