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