View Javadoc
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