X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Ftest%2FTestLauncher.java;fp=src%2Fbe%2Fnikiroo%2Futils%2Ftest%2FTestLauncher.java;h=ba4e81de22262f23a8e9161c2b2b81fec4413c15;hb=80383c142f85a7850d0fbea418689608fdccac05;hp=0000000000000000000000000000000000000000;hpb=2cce3dcb72c55aa5cca66e1398f23906e286abc8;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/test/TestLauncher.java b/src/be/nikiroo/utils/test/TestLauncher.java new file mode 100644 index 0000000..ba4e81d --- /dev/null +++ b/src/be/nikiroo/utils/test/TestLauncher.java @@ -0,0 +1,388 @@ +package be.nikiroo.utils.test; + +import java.util.ArrayList; +import java.util.List; + +/** + * A {@link TestLauncher} starts a series of {@link TestCase}s and displays the + * result to the user. + * + * @author niki + */ +public class TestLauncher { + /** + * {@link Exception} happening during the setup process. + * + * @author niki + */ + private class SetupException extends Exception { + private static final long serialVersionUID = 1L; + + public SetupException(Exception e) { + super(e); + } + } + + /** + * {@link Exception} happening during the tear-down process. + * + * @author niki + */ + private class TearDownException extends Exception { + private static final long serialVersionUID = 1L; + + public TearDownException(Exception e) { + super(e); + } + } + + private List series; + private List tests; + private int columns; + private String okString; + private String koString; + private String name; + private boolean cont; + + protected int executed; + protected int total; + + /** + * Create a new {@link TestLauncher} with default parameters. + * + * @param name + * the test suite name + * @param args + * the arguments to configure the number of columns and the ok/ko + * {@link String}s + */ + public TestLauncher(String name, String[] args) { + this.name = name; + + int cols = 80; + if (args != null && args.length >= 1) { + try { + cols = Integer.parseInt(args[0]); + } catch (NumberFormatException e) { + System.err.println("Test configuration: given number " + + "of columns is not parseable: " + args[0]); + } + } + + setColumns(cols); + + String okString = "[ ok ]"; + String koString = "[ !! ]"; + if (args != null && args.length >= 3) { + okString = args[1]; + koString = args[2]; + } + + setOkString(okString); + setKoString(koString); + + series = new ArrayList(); + tests = new ArrayList(); + cont = true; + } + + /** + * Called before actually starting the tests themselves. + * + * @throws Exception + * in case of error + */ + protected void start() throws Exception { + } + + /** + * Called when the tests are passed (or failed to do so). + * + * @throws Exception + * in case of error + */ + protected void stop() throws Exception { + } + + protected void addTest(TestCase test) { + tests.add(test); + } + + protected void addSeries(TestLauncher series) { + this.series.add(series); + } + + /** + * Launch the series of {@link TestCase}s and the {@link TestCase}s. + * + * @return the number of errors + */ + public int launch() { + return launch(0); + } + + /** + * Launch the series of {@link TestCase}s and the {@link TestCase}s. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + * + * @return the number of errors + */ + public int launch(int depth) { + int errors = 0; + executed = 0; + total = tests.size(); + + print(depth); + + try { + start(); + + errors += launchTests(depth); + if (tests.size() > 0 && depth == 0) { + System.out.println(""); + } + + for (TestLauncher serie : series) { + errors += serie.launch(depth + 1); + executed += serie.executed; + total += serie.total; + } + } catch (Exception e) { + print(depth, "__start"); + print(depth, e); + } finally { + try { + stop(); + } catch (Exception e) { + print(depth, "__stop"); + print(depth, e); + } + } + + print(depth, executed, errors, total); + + return errors; + } + + /** + * Launch the {@link TestCase}s. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + * + * @return the number of errors + */ + protected int launchTests(int depth) { + int errors = 0; + for (TestCase test : tests) { + print(depth, test.getName()); + + Exception ex = null; + try { + try { + test.setUp(); + } catch (Exception e) { + throw new SetupException(e); + } + test.test(); + try { + test.tearDown(); + } catch (Exception e) { + throw new TearDownException(e); + } + } catch (Exception e) { + ex = e; + } + + if (ex != null) { + errors++; + } + + print(depth, ex); + + executed++; + + if (ex != null && !cont) { + break; + } + } + + return errors; + } + + /** + * Specify a custom number of columns to use for the display of messages. + * + * @param columns + * the number of columns + */ + public void setColumns(int columns) { + this.columns = columns; + } + + /** + * Continue to run the tests when an error is detected. + * + * @param cont + * yes or no + */ + public void setContinueAfterFail(boolean cont) { + this.cont = cont; + } + + /** + * Set a custom "[ ok ]" {@link String} when a test passed. + * + * @param okString + * the {@link String} to display at the end of a success + */ + public void setOkString(String okString) { + this.okString = okString; + } + + /** + * Set a custom "[ !! ]" {@link String} when a test failed. + * + * @param koString + * the {@link String} to display at the end of a failure + */ + public void setKoString(String koString) { + this.koString = koString; + } + + /** + * Print the test suite header. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + */ + protected void print(int depth) { + if (depth == 0) { + System.out.println("[ Test suite: " + name + " ]"); + System.out.println(""); + } else { + System.out.println(prefix(depth) + name + ":"); + } + } + + /** + * Print the name of the {@link TestCase} we will start immediately after. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + * @param test + * the {@link TestCase} + */ + protected void print(int depth, String name) { + name = prefix(depth) + (name == null ? "" : name).replace("\t", " "); + + while (name.length() < columns - 11) { + name += "."; + } + + System.out.print(name); + } + + /** + * Print the result of the {@link TestCase} we just ran. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + * @param error + * the {@link Exception} it ran into if any + */ + private void print(int depth, Exception error) { + if (error != null) { + System.out.println(" " + koString); + for (String line : (error.getMessage() + "").split("\n")) { + System.out.println(prefix(depth) + "\t\t" + line); + } + } else { + System.out.println(" " + okString); + } + } + + /** + * Print the total result for this test suite. + * + * @param depth + * the level at which is the launcher (0 = main launcher) + * @param executed + * the number of tests actually ran + * @param errors + * the number of errors encountered + * @param total + * the total number of tests in the suite + */ + private void print(int depth, int executed, int errors, int total) { + int ok = executed - errors; + int pc = (int) ((100.0 * ok) / executed); + if (pc == 0 && ok > 0) { + pc = 1; + } + int pcTotal = (int) ((100.0 * ok) / total); + if (pcTotal == 0 && ok > 0) { + pcTotal = 1; + } + + String resume = "Tests passed: " + ok + "/" + executed + " (" + pc + + "%) on a total of " + total + " (" + pcTotal + "% total)"; + if (depth == 0) { + System.out.println(resume); + } else { + String arrow = "┗▶"; + if (series.isEmpty()) { + arrow = "━▶"; + } + System.out.println(prefix(depth) + arrow + resume); + } + } + + private int last = -1; + + /** + * Return the prefix to print before the current line. + * + * @param depth + * the current depth + * + * @return the prefix + */ + private String prefix(int depth) { + String space = tabs(depth - 1); + + String line = ""; + if (depth > 0) { + if (depth > 1) { + if (depth != last) { + line = "╻"; // first line + } else { + line = "┃"; // continuation + } + } + space = space + line + tabs(1); + } + + last = depth; + return space; + } + + /** + * Return the given number of space-converted tabs in a {@link String}. + * + * @param depth + * the number of tabs to return + * + * @return the string + */ + private String tabs(int depth) { + + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < depth; i++) { + builder.append(" "); + } + return builder.toString(); + } +}