Add some tests
[nikiroo-utils.git] / src / be / nikiroo / utils / test / TestLauncher.java
CommitLineData
80383c14
NR
1package be.nikiroo.utils.test;
2
3import java.util.ArrayList;
4import java.util.List;
5
6/**
7 * A {@link TestLauncher} starts a series of {@link TestCase}s and displays the
8 * result to the user.
9 *
10 * @author niki
11 */
12public class TestLauncher {
13 /**
14 * {@link Exception} happening during the setup process.
15 *
16 * @author niki
17 */
18 private class SetupException extends Exception {
19 private static final long serialVersionUID = 1L;
20
21 public SetupException(Exception e) {
22 super(e);
23 }
24 }
25
26 /**
27 * {@link Exception} happening during the tear-down process.
28 *
29 * @author niki
30 */
31 private class TearDownException extends Exception {
32 private static final long serialVersionUID = 1L;
33
34 public TearDownException(Exception e) {
35 super(e);
36 }
37 }
38
39 private List<TestLauncher> series;
40 private List<TestCase> tests;
41 private int columns;
42 private String okString;
43 private String koString;
44 private String name;
45 private boolean cont;
46
47 protected int executed;
48 protected int total;
49
50 /**
51 * Create a new {@link TestLauncher} with default parameters.
52 *
53 * @param name
54 * the test suite name
55 * @param args
56 * the arguments to configure the number of columns and the ok/ko
57 * {@link String}s
58 */
59 public TestLauncher(String name, String[] args) {
60 this.name = name;
61
62 int cols = 80;
63 if (args != null && args.length >= 1) {
64 try {
65 cols = Integer.parseInt(args[0]);
66 } catch (NumberFormatException e) {
67 System.err.println("Test configuration: given number "
68 + "of columns is not parseable: " + args[0]);
69 }
70 }
71
72 setColumns(cols);
73
74 String okString = "[ ok ]";
75 String koString = "[ !! ]";
76 if (args != null && args.length >= 3) {
77 okString = args[1];
78 koString = args[2];
79 }
80
81 setOkString(okString);
82 setKoString(koString);
83
84 series = new ArrayList<TestLauncher>();
85 tests = new ArrayList<TestCase>();
86 cont = true;
87 }
88
89 /**
90 * Called before actually starting the tests themselves.
91 *
92 * @throws Exception
93 * in case of error
94 */
95 protected void start() throws Exception {
96 }
97
98 /**
99 * Called when the tests are passed (or failed to do so).
100 *
101 * @throws Exception
102 * in case of error
103 */
104 protected void stop() throws Exception {
105 }
106
107 protected void addTest(TestCase test) {
108 tests.add(test);
109 }
110
111 protected void addSeries(TestLauncher series) {
112 this.series.add(series);
113 }
114
115 /**
116 * Launch the series of {@link TestCase}s and the {@link TestCase}s.
117 *
118 * @return the number of errors
119 */
120 public int launch() {
121 return launch(0);
122 }
123
124 /**
125 * Launch the series of {@link TestCase}s and the {@link TestCase}s.
126 *
127 * @param depth
128 * the level at which is the launcher (0 = main launcher)
129 *
130 * @return the number of errors
131 */
132 public int launch(int depth) {
133 int errors = 0;
134 executed = 0;
135 total = tests.size();
136
137 print(depth);
138
139 try {
140 start();
141
142 errors += launchTests(depth);
143 if (tests.size() > 0 && depth == 0) {
144 System.out.println("");
145 }
146
147 for (TestLauncher serie : series) {
148 errors += serie.launch(depth + 1);
149 executed += serie.executed;
150 total += serie.total;
151 }
152 } catch (Exception e) {
153 print(depth, "__start");
154 print(depth, e);
155 } finally {
156 try {
157 stop();
158 } catch (Exception e) {
159 print(depth, "__stop");
160 print(depth, e);
161 }
162 }
163
164 print(depth, executed, errors, total);
165
166 return errors;
167 }
168
169 /**
170 * Launch the {@link TestCase}s.
171 *
172 * @param depth
173 * the level at which is the launcher (0 = main launcher)
174 *
175 * @return the number of errors
176 */
177 protected int launchTests(int depth) {
178 int errors = 0;
179 for (TestCase test : tests) {
180 print(depth, test.getName());
181
182 Exception ex = null;
183 try {
184 try {
185 test.setUp();
186 } catch (Exception e) {
187 throw new SetupException(e);
188 }
189 test.test();
190 try {
191 test.tearDown();
192 } catch (Exception e) {
193 throw new TearDownException(e);
194 }
195 } catch (Exception e) {
196 ex = e;
197 }
198
199 if (ex != null) {
200 errors++;
201 }
202
203 print(depth, ex);
204
205 executed++;
206
207 if (ex != null && !cont) {
208 break;
209 }
210 }
211
212 return errors;
213 }
214
215 /**
216 * Specify a custom number of columns to use for the display of messages.
217 *
218 * @param columns
219 * the number of columns
220 */
221 public void setColumns(int columns) {
222 this.columns = columns;
223 }
224
225 /**
226 * Continue to run the tests when an error is detected.
227 *
228 * @param cont
229 * yes or no
230 */
231 public void setContinueAfterFail(boolean cont) {
232 this.cont = cont;
233 }
234
235 /**
236 * Set a custom "[ ok ]" {@link String} when a test passed.
237 *
238 * @param okString
239 * the {@link String} to display at the end of a success
240 */
241 public void setOkString(String okString) {
242 this.okString = okString;
243 }
244
245 /**
246 * Set a custom "[ !! ]" {@link String} when a test failed.
247 *
248 * @param koString
249 * the {@link String} to display at the end of a failure
250 */
251 public void setKoString(String koString) {
252 this.koString = koString;
253 }
254
255 /**
256 * Print the test suite header.
257 *
258 * @param depth
259 * the level at which is the launcher (0 = main launcher)
260 */
261 protected void print(int depth) {
262 if (depth == 0) {
263 System.out.println("[ Test suite: " + name + " ]");
264 System.out.println("");
265 } else {
266 System.out.println(prefix(depth) + name + ":");
267 }
268 }
269
270 /**
271 * Print the name of the {@link TestCase} we will start immediately after.
272 *
273 * @param depth
274 * the level at which is the launcher (0 = main launcher)
275 * @param test
276 * the {@link TestCase}
277 */
278 protected void print(int depth, String name) {
279 name = prefix(depth) + (name == null ? "" : name).replace("\t", " ");
280
281 while (name.length() < columns - 11) {
282 name += ".";
283 }
284
285 System.out.print(name);
286 }
287
288 /**
289 * Print the result of the {@link TestCase} we just ran.
290 *
291 * @param depth
292 * the level at which is the launcher (0 = main launcher)
293 * @param error
294 * the {@link Exception} it ran into if any
295 */
296 private void print(int depth, Exception error) {
297 if (error != null) {
298 System.out.println(" " + koString);
299 for (String line : (error.getMessage() + "").split("\n")) {
300 System.out.println(prefix(depth) + "\t\t" + line);
301 }
302 } else {
303 System.out.println(" " + okString);
304 }
305 }
306
307 /**
308 * Print the total result for this test suite.
309 *
310 * @param depth
311 * the level at which is the launcher (0 = main launcher)
312 * @param executed
313 * the number of tests actually ran
314 * @param errors
315 * the number of errors encountered
316 * @param total
317 * the total number of tests in the suite
318 */
319 private void print(int depth, int executed, int errors, int total) {
320 int ok = executed - errors;
321 int pc = (int) ((100.0 * ok) / executed);
322 if (pc == 0 && ok > 0) {
323 pc = 1;
324 }
325 int pcTotal = (int) ((100.0 * ok) / total);
326 if (pcTotal == 0 && ok > 0) {
327 pcTotal = 1;
328 }
329
330 String resume = "Tests passed: " + ok + "/" + executed + " (" + pc
331 + "%) on a total of " + total + " (" + pcTotal + "% total)";
332 if (depth == 0) {
333 System.out.println(resume);
334 } else {
335 String arrow = "┗▶";
336 if (series.isEmpty()) {
337 arrow = "━▶";
338 }
339 System.out.println(prefix(depth) + arrow + resume);
340 }
341 }
342
343 private int last = -1;
344
345 /**
346 * Return the prefix to print before the current line.
347 *
348 * @param depth
349 * the current depth
350 *
351 * @return the prefix
352 */
353 private String prefix(int depth) {
354 String space = tabs(depth - 1);
355
356 String line = "";
357 if (depth > 0) {
358 if (depth > 1) {
359 if (depth != last) {
360 line = "╻"; // first line
361 } else {
362 line = "┃"; // continuation
363 }
364 }
365 space = space + line + tabs(1);
366 }
367
368 last = depth;
369 return space;
370 }
371
372 /**
373 * Return the given number of space-converted tabs in a {@link String}.
374 *
375 * @param depth
376 * the number of tabs to return
377 *
378 * @return the string
379 */
380 private String tabs(int depth) {
381
382 StringBuilder builder = new StringBuilder();
383 for (int i = 0; i < depth; i++) {
384 builder.append(" ");
385 }
386 return builder.toString();
387 }
388}