1 package eu.simuline.testhelpers;
2
3 import org.junit.AssumptionViolatedException;
4 import org.junit.runner.Description;
5 import org.junit.runner.notification.Failure;
6 import org.junit.runner.notification.RunNotifier; //; for javadoc only
7
8 import junit.framework.AssertionFailedError; // NOPMD
9
10 import java.util.List;
11 import java.util.ArrayList;
12
13 /**
14 * Represents a test which may be a singular case or a suite
15 * during its lifetime
16 * from being scheduled to having runned successfully or not.
17 * In this respect, the testcase goes beyond a {@link Description}.
18 * It is a wrapper around a {@link Description} {@link #desc}
19 * but includes also the phase given by the quality {@link #qual}.
20 * If the test is a suite, the sub-tests are stored in {@link #children}.
21 * Each test has an index {@link #idxTest} which identifies the test.
22 * <p>
23 * Singular tests may be either {@link Quality#Scheduled},
24 * {@link Quality#Started} or finished in some sense.
25 * If finished, the test may be {@link Quality#Success}
26 * or it has been interrupted by an exception wrapped in a {@link #failure}.
27 * Also a timestamp {@link #time} is provided
28 * which indicates the running time of a singular test
29 * if it has been started and finished.
30 * <p>
31 * This class provides a single non-private constructor
32 * transforming a description of a test
33 * which may be singular or a suite into a hierarchy of {@link TestCase}s.
34 * <p>
35 * The methods are either
36 * <ul>
37 * <li>
38 * getter methods and their convenience methods like
39 * {@link #getDesc()}, {@link #isTest()}, {@link #testCount()},
40 * {@link #getChildren()}, {@link #getIdx()}, {@link #getQuality()},
41 * {@link #hasFailed()}, {@link #getThrown()} and {@link #getTime()}.
42 * <li>
43 * The special method {@link #fullSuccess()}
44 * indicating whether this testcase including all testcases succeeded.
45 * <li>
46 * methods triggering phase transitions like
47 * {@link #setScheduledRec()}, {@link #setQualStartedIgnored(Quality)},
48 * {@link #setFailure(Failure)}, {@link #setAssumptionFailure(Failure)}
49 * and {@link #setFinished()} tied to events given by {@link RunNotifier}.
50 * </ul>
51 *
52 * @see Quality
53 *
54 * Created: Wed Jun 12 16:41:14 2006
55 *
56 * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
57 * @version 1.0
58 */
59 class TestCase {
60
61 /* -------------------------------------------------------------------- *
62 * fields. *
63 * -------------------------------------------------------------------- */
64
65 /**
66 * The description of this testcase.
67 * This may be singular or not.
68 */
69 private final Description desc;
70
71 /**
72 * The list of children if this is a suite according to {@link #isTest()}
73 * or <code>null</code> if this is a (singular) test.
74 */
75 private final List<TestCase> children;
76
77 /**
78 * The index of this testcase if this is a (singular) test;
79 * otherwise <code>-1</code>.
80 * Each testcase is part of a tree hierarchy
81 * in which the singular tests have indices <code>0...n-1</code>
82 * if there are <code>n</code> singular test.
83 */
84 private final int idxTest;
85
86 /**
87 * The phase of this testcase.
88 * The phase is <code>null</code> iff this testcase is a suite.
89 * Otherwise,
90 * If this test is singular and newly created,
91 * the quality is {@link Quality#Scheduled}.
92 * State transitions are reigned by {@link Quality}.
93 */
94 private Quality qual;
95
96 /**
97 * The failure if any; otherwise <code>null</code>.
98 * This is <code>null</code> iff
99 * <ul>
100 * <li>
101 * this is a test suite according to {@link #isTest()}
102 * and thus {@link #qual} is <code>null</code>.
103 * <li>
104 * this is a singular test
105 * (thus the phase {@link #qual} is non-<code>null</code>)
106 * and a failure is associated with the phase
107 * according to {@link Quality#hasFailure()}.
108 * </ul>
109 */
110 private Failure failure;
111
112 // must be negative and different
113 private static final long TIME_SUITE = -3;
114 // must be negative and different
115 static final long TIME_SCHEDULED = -1;
116
117 /**
118 * If this testcase is a single testcase which has been finished,
119 * this is the span of time required to run this test.
120 * For single ignored tests, this is <code>0</code>
121 * and in the other cases, the value is negative.
122 * If this testcase is a suite, the result is {@link #TIME_SUITE};
123 * otherwise if scheduled it is {@link #TIME_SCHEDULED},
124 * both negative.
125 *
126 * @see Quality#setTime(long)
127 */
128 // **** really needed? ****
129 private long time;
130
131 /* -------------------------------------------------------------------- *
132 * constructor. *
133 * -------------------------------------------------------------------- */
134
135 /**
136 * Creates a new testcase based on the description <code>desc</code>
137 * with indices <code>0...n-1</code>,
138 * where <code>n</code> is the testcout of <code>desc</code>.
139 * This testcase is the root of a hierarchy of tests
140 * which consists of a single test if <code>desc</code> is singular.
141 * <p>
142 * All singular tests have quality {@link Quality#Scheduled}.
143 *
144 * @param desc
145 * a description of this testcase.
146 * This may be singular or a test suite.
147 */
148 TestCase(Description desc) {
149 this(desc, 0);
150 }
151
152 /**
153 * Creates a new testcase based on the description <code>desc</code>
154 * with indices <code>idxTest...idxTest+n-1</code>,
155 * where <code>n</code> is the testcout of <code>desc</code>.
156 * This testcase is the root of a hierarchy of tests
157 * which consists of a single test if <code>desc</code> is singular.
158 * <p>
159 * All singular tests have quality {@link Quality#Scheduled}.
160 *
161 * @param desc
162 * a description of this testcase.
163 * This may be singular or a test suite.
164 * @param idxTest
165 * the minimal index of the (singular) tests to be created.
166 * To create the root of
167 */
168 private TestCase(Description desc, int idxTest) {
169 this.desc = desc;
170
171 if (desc.isTest()) {
172 this.children = null;
173 this.idxTest = idxTest;
174 this.qual = Quality.Scheduled;
175 this.failure = null;
176 assert this.qual.hasFailure() == (this.failure != null);
177 // For Quality.Scheduled, the result of setTime
178 // is always TIME_SCHEDULED
179 // this.time = this.qual.setTime(this.time);
180 this.time = TIME_SCHEDULED;
181 } else {
182 this.children = new ArrayList<TestCase>();
183 for (Description descChild : desc.getChildren()) {
184 this.children.add(new TestCase(descChild, idxTest));
185 idxTest += descChild.testCount();
186 }
187 this.idxTest = -1;
188 this.qual = null;
189 this.failure = null;
190 this.time = TIME_SUITE;
191 }
192 }
193
194
195 /* -------------------------------------------------------------------- *
196 * methods. *
197 * -------------------------------------------------------------------- */
198
199
200 /**
201 * Returns the description of this testcase given by {@link #desc}.
202 * This may be singular or not.
203 *
204 * @return
205 * {@link #desc}.
206 */
207 Description getDesc() {
208 return this.desc;
209 }
210
211 /**
212 * Returns whether this testcase is a (singular) test.
213 * Otherwise it is a testsuite.
214 * This is a frequently used convenience method.
215 *
216 * @return
217 * this testcase is a (singular) test.
218 * @see Description#isTest()
219 */
220 boolean isTest() {
221 return this.desc.isTest();
222 }
223
224 /**
225 * Returns the number of singular test in this testcase.
226 * This is <code>1</code>
227 * iff this is a singular test according to {@link #isTest()}.
228 * This is a convenience method used once.
229 *
230 * @return
231 * the number of singular test in this testcase.
232 * @see Description#testCount()
233 */
234 int testCount() {
235 return this.desc.testCount();
236 }
237
238 /**
239 * Returns the list of children given by {@link #children}.
240 *
241 * @return
242 * <ul>
243 * <li><code>null</code>
244 * if this is a test suite according to {@link #isTest()}.
245 * <li> the list of children of this suite otherwise.
246 * </ul>
247 */
248 List<TestCase> getChildren() {
249 return this.children;
250 }
251
252 /**
253 * Returns the index of this testcase as described in {@link #idxTest}.
254 *
255 * @return
256 * {@link #idxTest}
257 */
258 int getIdx() {
259 return this.idxTest;
260 }
261
262 /**
263 * Returns the phase of this testcase as described in {@link #qual}.
264 *
265 * @return
266 * {@link #qual}
267 */
268 Quality getQuality() {
269 return this.qual;
270 }
271
272 /**
273 * Returns whether {@link #getThrown()} returns non-<code>null</code>.
274 * For singular tests,
275 * returns the same value as {@link Quality#hasFailure()}
276 * applied to {@link #qual}.
277 * This is a convenience method.
278 *
279 * @return
280 * whether {@link #getThrown()} returns non-<code>null</code>.
281 * @see #getThrown()
282 */
283 boolean hasFailed() {
284 assert isTest();
285 assert this.qual.hasFailure() == (getThrown() != null);
286 return getThrown() != null;
287 }
288
289 /**
290 * Returns <code>null</code> if no failure occurred.
291 * It returns non-<code>null</code> iff {@link #qual} is
292 * {@link Quality#Error}, {@link Quality#Failure} or
293 * {@link Quality#Invalidated}.
294 * In particular, it returns <code>null</code>
295 * if {@link #qual} is <code>null</code>.
296 *
297 * @return
298 * the throwable defined by {@link #failure}
299 * if this is non-<code>null</code>, otherwise the throwable
300 * defined by {@link Failure#getException()}.
301 * @see Failure#getException()
302 */
303 Throwable getThrown() {
304 assert this.qual.hasFailure() == (this.failure != null);
305 return this.failure == null ? null : this.failure.getException();
306 }
307
308 // **** nowhere needed.
309 /**
310 * If this testcase is a single testcase which has been finished,
311 * this is the span of time required to run this test.
312 * For single ignored tests, this is <code>0</code>
313 * and in the other cases, the value is negative.
314 * If this testcase is a suite, the result is <code>-3</code>.
315 *
316 * @return
317 * {@link #time}
318 * @see Quality#setTime(long)
319 */
320 // **** nowhere used. ****
321 long getTime() {
322 return this.time;
323 }
324
325 /**
326 * Returns whether this test including all sub-tests succeeded.
327 *
328 * @return
329 * whether this test including all sub-tests succeeded.
330 */
331 boolean fullSuccess() {
332 if (isTest()) {
333 return getQuality() == Quality.Success;
334 }
335 assert !isTest();
336 for (TestCase child : this.children) {
337 if (!child.fullSuccess()) {
338 // Here, at least one child did not have full success
339 return false;
340 }
341 }
342 // Here, all children had full success
343 return true;
344 }
345
346 /**
347 * Recursively
348 * triggers a transition of the current phase to {@link Quality#Scheduled}:
349 * If this is a test, just set {@link #qual} to {@link Quality#Scheduled}
350 * if possible updating {@link #failure} and {@link #time}.
351 * If this is a suite, go recursively into the {@link #children}.
352 *
353 * @throws IllegalStateException
354 * if it is not allowed to set the phase to {@link Quality#Scheduled}
355 * according to {@link Quality#setScheduled()}.
356 */
357 void setScheduledRec() {
358 if (this.isTest()) {
359 assert this.qual != null;
360 this.qual = this.qual.setScheduled();
361 this.failure = null;
362 assert this.qual.hasFailure() == (this.failure != null);
363 this.time = this.qual.setTime(this.time);
364
365 return;
366 }
367 // Here, this is a suite
368 assert this.children != null;
369 for (TestCase child : this.children) {
370 child.setScheduledRec();
371 }
372 }
373
374 /**
375 * Triggers a transition of the current phase to <code>qual</code>
376 * if possible; otherwise throws an exception.
377 *
378 * @param qual
379 * either {@link Quality#Started} or {@link Quality#Ignored}.
380 * @throws IllegalStateException
381 * <ul>
382 * <li>
383 * if <code>qual</code> is
384 * neither {@link Quality#Started} nor {@link Quality#Ignored}
385 * and in particular if it is <code>null</code> .
386 * <li>
387 * if {@link #qual} does not allow the transition to <code>qual</code>.
388 * </ul>
389 * @throws NullPointerException
390 * if this is a test suite and thus {@link #qual} is <code>null</code>.
391 */
392 void setQualStartedIgnored(Quality qual) {
393 // The following assertions are not allowed
394 // because conditions are checked in the code
395 // assert this.qual == Quality.Scheduled;
396 // assert qual == Quality.Started || qual == Quality.Ignored;
397
398 // set this.qual or throw an exception.
399 switch (qual) {
400 case Started:
401 // throws IllegalStateException for this.qual == Started
402 this.qual = this.qual.setStarted();
403 break;
404 case Ignored:
405 // throws IllegalStateException for this.qual != Scheduled
406 this.qual = this.qual.setIgnored();
407 break;
408 default:
409 throw new IllegalStateException
410 ("Unexpected phase transition to " + qual + ". ");
411 }
412 // Here, setting qual succeeded
413 assert this.qual == qual;
414
415 this.failure = null;
416 assert this.qual.hasFailure() == (this.failure != null);
417 this.time = this.qual.setTime(this.time);
418 }
419
420 /**
421 * Triggers a transition of the current phase
422 * if the non-assumption failure <code>failure</code> occurs.
423 * This presupposes that this is a singular test
424 * and that the current phase is {@link Quality#Started}.
425 * The new phase is either {@link Quality#Failure} or {@link Quality#Error}
426 * depending on whether the thrown throwable
427 * is an {@link AssertionError}
428 * (should be {@link AssertionFailedError}).
429 *
430 * @param failure
431 * a failure representing either a proper failure or an error
432 * but not an assumption failure.
433 * @throws IllegalStateException
434 * if <code>failure</code>
435 * represents an {@link AssumptionViolatedException}
436 * via {@link Failure#getException()}.
437 * @throws IllegalStateException
438 * if <code>failure</code>
439 * does not represent an {@link AssumptionViolatedException},
440 * this is a singular test and
441 * {@link #qual} does not allow the transition
442 * given by a failure <code>failure</code>,
443 * i.e. {@link #qual} is not {@link Quality#Started}.
444 * @throws NullPointerException
445 * if <code>failure</code>
446 * does not represent an {@link AssumptionViolatedException} and
447 * this is a test suite and thus {@link #qual} is <code>null</code>.
448 * @see #setAssumptionFailure(Failure)
449 * @see Quality#setFailure(Throwable)
450 */
451 void setFailure(Failure failure) {
452 assert failure != null;
453 //assert this.desc == this.failure.getDescription();
454 this.failure = failure;
455 Throwable thrw = failure.getException();
456 if (thrw instanceof AssumptionViolatedException) {
457 throw new IllegalStateException
458 ("Found unexpected AssumptionViolatedException. ");
459 }
460 this.qual = this.qual.setFailure(thrw);
461 assert this.qual.hasFailure();
462 assert this.qual.hasFailure() == (this.failure != null);
463 // deferred to setFinished()
464 // this.time = this.qual.setTime(this.time);
465 }
466
467 /**
468 * Triggers a transition of the current phase
469 * if the assumption failure <code>failure</code> occurs.
470 * This presupposes that this is a singular test
471 * and that the current phase is {@link Quality#Started}.
472 * The new phase is {@link Quality#Invalidated}.
473 *
474 * @param failure
475 * a failure representing a failed assumption.
476 * @throws IllegalStateException
477 * if <code>failure</code>
478 * does not represent an {@link AssumptionViolatedException}
479 * via {@link Failure#getException()}.
480 * @throws IllegalStateException
481 * if <code>failure</code>
482 * represents an {@link AssumptionViolatedException},
483 * this is a singlar test and
484 * {@link #qual} does not allow the transition
485 * to {@link Quality#Invalidated}.
486 * @throws NullPointerException
487 * if <code>failure</code>
488 * represents an {@link AssumptionViolatedException} and
489 * this is a test suite and thus {@link #qual} is <code>null</code>.
490 * @see #setFailure(Failure)
491 */
492 void setAssumptionFailure(Failure failure) {
493 assert failure != null;
494 //assert this.desc == this.failure.getDescription();
495 if (!(failure.getException() instanceof AssumptionViolatedException)) {
496 throw new IllegalStateException
497 ("Expected AssumptionViolatedException but found " +
498 failure.getException() + ". ");
499 }
500 this.failure = failure;
501 this.qual = this.qual.setAssumptionFailure();
502 assert this.qual.hasFailure();
503 assert this.qual.hasFailure() == (this.failure != null);
504 // deferred to setFinished()
505 // this.time = this.qual.setTime(this.time);
506 }
507
508 /**
509 * Triggers a transition of the current phase
510 * given by finishing the run of a singular testcase if possible
511 * as specified by {@link Quality#setFinished()}.
512 * The new phase is
513 * <ul>
514 * <li>remains unchanged for phases {@link Quality#Failure},
515 * {@link Quality#Invalidated} and {@link Quality#Error}
516 * <li>{@link Quality#Success} for current phase {@link Quality#Started}.
517 * </ul>
518 * Note that this sets {@link #time}
519 * to the time required to run this testcase.
520 *
521 * @throws IllegalStateException
522 * if this is a singular test and
523 * {@link #qual} does not allow the transition
524 * by finishing a test case.
525 * This is the case for states {@link Quality#Scheduled},
526 * {@link Quality#Success} and {@link Quality#Ignored}.
527 * @throws NullPointerException
528 * if this is a test suite and thus {@link #qual} is <code>null</code>.
529 * @see #setFailure(Failure)
530 */
531 void setFinished() {
532 assert this.qual.hasFailure() == (this.failure != null);
533 // does not change anything if there has been a failure.
534 this.qual = this.qual.setFinished();
535 assert this.qual.hasFailure() == (this.failure != null);
536 this.time = this.qual.setTime(this.time);
537 }
538
539 /**
540 * Returns the string representation of {@link #desc} for suites
541 * and a representation including {@link #qual} for singular tests.
542 */
543 public String toString() {
544 // StringBuilder res = new StringBuilder();
545 // if (isTest()) {
546 // res.append("<Test desc='");
547 // res.append(this.desc);
548 // res.append("' idxTest='");
549 // res.append(this.idxTest);
550 // res.append("' qual='");
551 // res.append(this.qual);
552 // res.append("'/>");
553 // // **** rework needed.
554 // return res.toString();
555 // }
556 // // Here, this is a suite.
557 // res.append("<Suite>");
558 // for (TestCase child : this.children) {
559 // res.append(child.toString());
560 // }
561 // res.append("</Suite>");
562
563
564 assert isTest() == (this.qual != null);
565 return isTest()
566 ? this.qual + ": " + this.desc.toString()
567 : this.desc.toString();
568 }
569
570 } // class TestCase