X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;ds=sidebyside;f=src%2Fbe%2Fnikiroo%2Futils%2Ftest%2FTestLauncher.java;fp=src%2Fbe%2Fnikiroo%2Futils%2Ftest%2FTestLauncher.java;h=895b565ae0f607522e494d92c8bc87541c672f51;hb=d46b7b96f94e88a776bcd2dfd756549ffb300cc9;hp=0000000000000000000000000000000000000000;hpb=c9994f27667bc421bcd448d39e55774fddf5c431;p=fanfix.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..895b565 --- /dev/null +++ b/src/be/nikiroo/utils/test/TestLauncher.java @@ -0,0 +1,434 @@ +package be.nikiroo.utils.test; + +import java.io.PrintWriter; +import java.io.StringWriter; +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(Throwable 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(Throwable e) { + super(e); + } + } + + private List series; + private List tests; + private TestLauncher parent; + + private int columns; + private String okString; + private String koString; + private String name; + private boolean cont; + + protected int executed; + protected int total; + + private int currentSeries = 0; + private boolean details = false; + + /** + * 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; + } + + /** + * Display the details of the errors + * + * @return TRUE to display them, false to simply mark the test as failed + */ + public boolean isDetails() { + if (parent != null) { + return parent.isDetails(); + } + + return details; + } + + /** + * Display the details of the errors + * + * @param details + * TRUE to display them, false to simply mark the test as failed + */ + public void setDetails(boolean details) { + if (parent != null) { + parent.setDetails(details); + } + + this.details = details; + } + + /** + * 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); + series.parent = this; + } + + /** + * 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(""); + } + + currentSeries = 0; + for (TestLauncher serie : series) { + errors += serie.launch(depth + 1); + executed += serie.executed; + total += serie.total; + currentSeries++; + } + } 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()); + + Throwable ex = null; + try { + try { + test.setUp(); + } catch (Throwable e) { + throw new SetupException(e); + } + test.test(); + try { + test.tearDown(); + } catch (Throwable e) { + throw new TearDownException(e); + } + } catch (Throwable 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, false) + 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 name + * the {@link TestCase} name + */ + protected void print(int depth, String name) { + name = prefix(depth, false) + + (name == null ? "" : name).replace("\t", " "); + + StringBuilder dots = new StringBuilder(); + while ((name.length() + dots.length()) < columns - 11) { + dots.append('.'); + } + + System.out.print(name + dots.toString()); + } + + /** + * 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, Throwable error) { + if (error != null) { + System.out.println(" " + koString); + if (isDetails()) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + error.printStackTrace(pw); + String lines = sw.toString(); + for (String line : lines.split("\n")) { + System.out.println(prefix(depth, false) + "\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 = "┗▶ "; + System.out.println(prefix(depth, currentSeries == 0) + arrow + + resume); + System.out.println(prefix(depth, currentSeries == 0)); + } + } + + private int last = -1; + + /** + * Return the prefix to print before the current line. + * + * @param depth + * the current depth + * @param first + * this line is the first of its tabulation level + * + * @return the prefix + */ + private String prefix(int depth, boolean first) { + String space = tabs(depth - 1); + + String line = ""; + if (depth > 0) { + if (depth > 1) { + if (depth != last && first) { + line = "╻"; // first line + } else { + line = "┃"; // continuation + } + } + + 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(); + } +}