Update Server, breaks API + remove deprecated
[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
8/**
9 * A {@link TestLauncher} starts a series of {@link TestCase}s and displays the
10 * result to the user.
11 *
12 * @author niki
13 */
14public class TestLauncher {
15 /**
16 * {@link Exception} happening during the setup process.
17 *
18 * @author niki
19 */
20 private class SetupException extends Exception {
21 private static final long serialVersionUID = 1L;
22
e9ca6bb8 23 public SetupException(Throwable e) {
80383c14
NR
24 super(e);
25 }
26 }
27
28 /**
29 * {@link Exception} happening during the tear-down process.
30 *
31 * @author niki
32 */
33 private class TearDownException extends Exception {
34 private static final long serialVersionUID = 1L;
35
e9ca6bb8 36 public TearDownException(Throwable e) {
80383c14
NR
37 super(e);
38 }
39 }
40
41 private List<TestLauncher> series;
42 private List<TestCase> tests;
43 private int columns;
44 private String okString;
45 private String koString;
46 private String name;
47 private boolean cont;
48
49 protected int executed;
50 protected int total;
51
86057589
NR
52 private int currentSeries = 0;
53
80383c14
NR
54 /**
55 * Create a new {@link TestLauncher} with default parameters.
56 *
57 * @param name
58 * the test suite name
59 * @param args
60 * the arguments to configure the number of columns and the ok/ko
61 * {@link String}s
62 */
63 public TestLauncher(String name, String[] args) {
64 this.name = name;
65
66 int cols = 80;
67 if (args != null && args.length >= 1) {
68 try {
69 cols = Integer.parseInt(args[0]);
70 } catch (NumberFormatException e) {
71 System.err.println("Test configuration: given number "
72 + "of columns is not parseable: " + args[0]);
73 }
74 }
75
76 setColumns(cols);
77
78 String okString = "[ ok ]";
79 String koString = "[ !! ]";
80 if (args != null && args.length >= 3) {
81 okString = args[1];
82 koString = args[2];
83 }
84
85 setOkString(okString);
86 setKoString(koString);
87
88 series = new ArrayList<TestLauncher>();
89 tests = new ArrayList<TestCase>();
90 cont = true;
91 }
92
93 /**
94 * Called before actually starting the tests themselves.
95 *
96 * @throws Exception
97 * in case of error
98 */
79ce1a49 99 @SuppressWarnings("unused")
80383c14
NR
100 protected void start() throws Exception {
101 }
102
103 /**
104 * Called when the tests are passed (or failed to do so).
105 *
106 * @throws Exception
107 * in case of error
108 */
79ce1a49 109 @SuppressWarnings("unused")
80383c14
NR
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
e9ca6bb8 190 Throwable ex = null;
80383c14
NR
191 try {
192 try {
193 test.setUp();
e9ca6bb8 194 } catch (Throwable e) {
80383c14
NR
195 throw new SetupException(e);
196 }
197 test.test();
198 try {
199 test.tearDown();
e9ca6bb8 200 } catch (Throwable e) {
80383c14
NR
201 throw new TearDownException(e);
202 }
e9ca6bb8 203 } catch (Throwable e) {
80383c14
NR
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)
db31c358
NR
283 * @param name
284 * the {@link TestCase} name
80383c14
NR
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 */
e9ca6bb8 305 private void print(int depth, Throwable error) {
80383c14
NR
306 if (error != null) {
307 System.out.println(" " + koString);
16d59378
NR
308 StringWriter sw = new StringWriter();
309 PrintWriter pw = new PrintWriter(sw);
310 error.printStackTrace(pw);
311 String lines = sw.toString();
86057589
NR
312 for (String line : lines.split("\n")) {
313 System.out.println(prefix(depth, false) + "\t\t" + line);
80383c14
NR
314 }
315 } else {
316 System.out.println(" " + okString);
317 }
318 }
319
320 /**
321 * Print the total result for this test suite.
322 *
323 * @param depth
324 * the level at which is the launcher (0 = main launcher)
325 * @param executed
326 * the number of tests actually ran
327 * @param errors
328 * the number of errors encountered
329 * @param total
330 * the total number of tests in the suite
331 */
332 private void print(int depth, int executed, int errors, int total) {
333 int ok = executed - errors;
334 int pc = (int) ((100.0 * ok) / executed);
335 if (pc == 0 && ok > 0) {
336 pc = 1;
337 }
338 int pcTotal = (int) ((100.0 * ok) / total);
339 if (pcTotal == 0 && ok > 0) {
340 pcTotal = 1;
341 }
342
343 String resume = "Tests passed: " + ok + "/" + executed + " (" + pc
344 + "%) on a total of " + total + " (" + pcTotal + "% total)";
345 if (depth == 0) {
346 System.out.println(resume);
347 } else {
86057589
NR
348 String arrow = "┗▶ ";
349 System.out.println(prefix(depth, currentSeries == 0) + arrow
350 + resume);
351 System.out.println(prefix(depth, currentSeries == 0));
80383c14
NR
352 }
353 }
354
355 private int last = -1;
356
357 /**
358 * Return the prefix to print before the current line.
359 *
360 * @param depth
361 * the current depth
86057589
NR
362 * @param first
363 * this line is the first of its tabulation level
80383c14
NR
364 *
365 * @return the prefix
366 */
86057589 367 private String prefix(int depth, boolean first) {
80383c14
NR
368 String space = tabs(depth - 1);
369
370 String line = "";
371 if (depth > 0) {
372 if (depth > 1) {
86057589 373 if (depth != last && first) {
80383c14
NR
374 line = "╻"; // first line
375 } else {
376 line = "┃"; // continuation
377 }
378 }
86057589
NR
379
380 space += line + tabs(1);
80383c14
NR
381 }
382
383 last = depth;
384 return space;
385 }
386
387 /**
388 * Return the given number of space-converted tabs in a {@link String}.
389 *
390 * @param depth
391 * the number of tabs to return
392 *
393 * @return the string
394 */
395 private String tabs(int depth) {
80383c14
NR
396 StringBuilder builder = new StringBuilder();
397 for (int i = 0; i < depth; i++) {
398 builder.append(" ");
399 }
400 return builder.toString();
401 }
402}