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