From 80383c142f85a7850d0fbea418689608fdccac05 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Mon, 13 Feb 2017 21:58:15 +0100 Subject: [PATCH] Add some tests --- Makefile.base | 19 +- configure.sh | 15 +- src/be/nikiroo/utils/resources/Bundle.java | 23 +- .../nikiroo/utils/resources/TransBundle.java | 5 + src/be/nikiroo/utils/test/BundleTest.java | 135 ++++++ src/be/nikiroo/utils/test/Test.java | 24 ++ src/be/nikiroo/utils/test/TestCase.java | 238 +++++++++++ src/be/nikiroo/utils/test/TestLauncher.java | 388 ++++++++++++++++++ .../nikiroo/utils/test/bundle_test.properties | 3 + 9 files changed, 837 insertions(+), 13 deletions(-) create mode 100644 src/be/nikiroo/utils/test/BundleTest.java create mode 100644 src/be/nikiroo/utils/test/Test.java create mode 100644 src/be/nikiroo/utils/test/TestCase.java create mode 100644 src/be/nikiroo/utils/test/TestLauncher.java create mode 100644 src/be/nikiroo/utils/test/bundle_test.properties diff --git a/Makefile.base b/Makefile.base index 559b89d9..99016440 100644 --- a/Makefile.base +++ b/Makefile.base @@ -7,6 +7,7 @@ #TEST = path to main test source to compile #JAR_FLAGS += a list of things to pack, each usually prefixed with "-C bin/" #SJAR_FLAGS += a list of things to pack, each usually prefixed with "-C src/", for *-sources.jar files +#TEST_PARAMS = any parameter to pass to the test runnable when "test-run" JAVAC = javac JAVAC_FLAGS += -encoding UTF-8 -d ./bin/ -cp ./src/ @@ -34,7 +35,7 @@ RJAR_FLAGS += -jar all: build jar -.PHONY: all clean mrproper mrpropre build run jrun jar resources install libs love +.PHONY: all clean mrproper mrpropre build run jrun jar resources test-resources install libs love bin: @mkdir -p bin @@ -50,7 +51,7 @@ build: resources $(JAVAC) $(JAVAC_FLAGS) "src/$$sup.java" ; \ done -test: +test: test-resources @[ -e bin/$(MAIN).class ] || echo You need to build the sources @[ -e bin/$(MAIN).class ] @echo Compiling test program... @@ -86,7 +87,17 @@ love: resources: libs @echo Copying resources into bin/... - @cd src && find . | grep -v '\.java$$' | while read -r ln; do \ + @cd src && find . | grep -v '\.java$$' | grep -v '/test/' | while read -r ln; do \ + if [ -f "$$ln" ]; then \ + dir="`dirname "$$ln"`"; \ + mkdir -p "../bin/$$dir" ; \ + cp "$$ln" "../bin/$$ln" ; \ + fi ; \ + done + +test-resources: resources + @echo Copying test resources into bin/... + @cd src && find . | grep -v '\.java$$' | grep '/test/' | while read -r ln; do \ if [ -f "$$ln" ]; then \ dir="`dirname "$$ln"`"; \ mkdir -p "../bin/$$dir" ; \ @@ -134,7 +145,7 @@ run-test: @[ "$(TEST)" = "" -o -e "bin/$(TEST).class" ] @echo Running tests for "$(NAME)"... @[ "$(TEST)" != "" ] || echo No test sources defined. - [ "$(TEST)" = "" ] || $(JAVA) $(JAVA_FLAGS) $(TEST) + [ "$(TEST)" = "" ] || ( clear ; $(JAVA) $(JAVA_FLAGS) $(TEST) $(TEST_PARAMS) ) install: @[ -e $(NAME).jar ] || echo You need to build the jar diff --git a/configure.sh b/configure.sh index ed494b7d..026db922 100755 --- a/configure.sh +++ b/configure.sh @@ -33,8 +33,21 @@ done [ $valid = false ] && exit 2 +if [ "`whereis tput`" = "tput:" ]; then + ok='"[ ok ]"'; + ko='"[ !! ]"'; + cols=80; +else + ok='"`tput bold`[`tput setf 2` OK `tput init``tput bold`]`tput init`"'; + ko='"`tput bold`[`tput setf 4` !! `tput init``tput bold`]`tput init`"'; + cols='"`tput cols`"'; +fi; + + echo "MAIN = be/nikiroo/utils/resources/TransBundle" > Makefile -echo "MORE = be/nikiroo/utils/StringUtils be/nikiroo/utils/IOUtils be/nikiroo/utils/MarkableFileInputStream" >> Makefile +echo "MORE = be/nikiroo/utils/StringUtils be/nikiroo/utils/IOUtils be/nikiroo/utils/MarkableFileInputStream be/nikiroo/utils/test/TestLauncher" >> Makefile +echo "TEST = be/nikiroo/utils/test/Test" >> Makefile +echo "TEST_PARAMS = $cols $ok $ko" >> Makefile echo "NAME = nikiroo-utils" >> Makefile echo "PREFIX = $PREFIX" >> Makefile echo "JAR_FLAGS += -C bin/ be" >> Makefile diff --git a/src/be/nikiroo/utils/resources/Bundle.java b/src/be/nikiroo/utils/resources/Bundle.java index 78d73a81..2039d578 100644 --- a/src/be/nikiroo/utils/resources/Bundle.java +++ b/src/be/nikiroo/utils/resources/Bundle.java @@ -57,7 +57,7 @@ public class Bundle> { * resource file) */ public String getString(E id) { - return getStringX(id, ""); + return getStringX(id, null); } /** @@ -74,8 +74,7 @@ public class Bundle> { */ public String getStringX(E id, String suffix) { String key = id.name() - + ((suffix == null || suffix.isEmpty()) ? "" : "_" - + suffix.toUpperCase()); + + (suffix == null ? "" : "_" + suffix.toUpperCase()); if (containsKey(key)) { return getString(key).trim(); @@ -180,10 +179,11 @@ public class Bundle> { } /** - * Create/update the .properties file. Will use the most likely candidate as - * base if the file does not already exists and this resource is - * translatable (for instance, "en_US" will use "en" as a base if the - * resource is a translation file). + * Create/update the .properties file. + *

+ * Will use the most likely candidate as base if the file does not already + * exists and this resource is translatable (for instance, "en_US" will use + * "en" as a base if the resource is a translation file). * * @param path * the path where the .properties files are @@ -219,6 +219,13 @@ public class Bundle> { writer.close(); } + /** + * Reload the {@link Bundle} data files. + */ + public void reload() { + setBundle(name, null); + } + /** * Check if the internal map contains the given key. * @@ -367,7 +374,7 @@ public class Bundle> { value = ""; } - String[] lines = value.replaceAll("\\\t", "\\\\\\t").split("\n"); + String[] lines = value.replaceAll("\t", "\\t").split("\n"); for (int i = 0; i < lines.length; i++) { writer.write(lines[i]); if (i < lines.length - 1) { diff --git a/src/be/nikiroo/utils/resources/TransBundle.java b/src/be/nikiroo/utils/resources/TransBundle.java index 61bc9225..a3804401 100644 --- a/src/be/nikiroo/utils/resources/TransBundle.java +++ b/src/be/nikiroo/utils/resources/TransBundle.java @@ -174,6 +174,11 @@ public class TransBundle> extends Bundle { setBundle(name, locale); } + @Override + public void reload() { + setBundle(name, locale); + } + @Override public String getString(E id) { return getString(id, (Object[]) null); diff --git a/src/be/nikiroo/utils/test/BundleTest.java b/src/be/nikiroo/utils/test/BundleTest.java new file mode 100644 index 00000000..6a2fdaa5 --- /dev/null +++ b/src/be/nikiroo/utils/test/BundleTest.java @@ -0,0 +1,135 @@ +package be.nikiroo.utils.test; + +import java.io.File; + +import be.nikiroo.utils.IOUtils; +import be.nikiroo.utils.resources.Bundle; +import be.nikiroo.utils.resources.Bundles; +import be.nikiroo.utils.resources.Meta; + +public class BundleTest extends TestLauncher { + private File tmp; + private B b = new B(); + + protected boolean isMain() { + return true; + } + + public BundleTest(String[] args) { + this("Bundle test", args); + } + + protected BundleTest(String name, String[] args) { + super(name, args); + + addTests(); + + if (isMain()) { + addSeries(new BundleTest("After saving/reloading the resources", + args) { + @Override + protected void start() throws Exception { + tmp = File.createTempFile("nikiroo-utils", ".test"); + tmp.delete(); + tmp.mkdir(); + b.updateFile(tmp.getAbsolutePath()); + Bundles.setDirectory(tmp.getAbsolutePath()); + b.reload(); + } + + @Override + protected void stop() { + IOUtils.deltree(tmp); + } + + @Override + protected boolean isMain() { + return false; + } + }); + } + } + + private void addTests() { + String pre = ""; + + addTest(new TestCase(pre + "getString simple") { + @Override + public void test() throws Exception { + assertEquals("un", b.getString(E.ONE)); + } + }); + + addTest(new TestCase(pre + "getStringX with null suffix") { + @Override + public void test() throws Exception { + assertEquals("un", b.getStringX(E.ONE, null)); + } + }); + + addTest(new TestCase(pre + "getStringX with empty suffix") { + @Override + public void test() throws Exception { + assertEquals(null, b.getStringX(E.ONE, "")); + } + }); + + addTest(new TestCase(pre + "getStringX with existing suffix") { + @Override + public void test() throws Exception { + assertEquals("un + suffix", b.getStringX(E.ONE, "suffix")); + } + }); + + addTest(new TestCase(pre + "getStringX with not existing suffix") { + @Override + public void test() throws Exception { + assertEquals(null, b.getStringX(E.ONE, "fake")); + } + }); + + addTest(new TestCase(pre + "getString with UTF-8 content") { + @Override + public void test() throws Exception { + assertEquals("日本語 Nihongo", b.getString(E.JAPANESE)); + } + }); + } + + /** + * {@link Bundle}. + * + * @author niki + */ + private class B extends Bundle { + protected B() { + super(E.class, N.bundle_test); + } + + } + + /** + * Key enum for the {@link Bundle}. + * + * @author niki + */ + private enum E { + @Meta(what = "", where = "", format = "", info = "") + ONE, // + @Meta(what = "", where = "", format = "", info = "") + ONE_SUFFIX, // + @Meta(what = "", where = "", format = "", info = "") + TWO, // + @Meta(what = "", where = "", format = "", info = "") + JAPANESE + } + + /** + * Name enum for the {@link Bundle}. + * + * @author niki + */ + private enum N { + bundle_test + } +} diff --git a/src/be/nikiroo/utils/test/Test.java b/src/be/nikiroo/utils/test/Test.java new file mode 100644 index 00000000..0804039e --- /dev/null +++ b/src/be/nikiroo/utils/test/Test.java @@ -0,0 +1,24 @@ +package be.nikiroo.utils.test; + +/** + * Tests for nikiroo-utils. + * + * @author niki + */ +public class Test extends TestLauncher { + public Test(String[] args) { + super("Nikiroo-utils", args); + + addSeries(new BundleTest(args)); + } + + /** + * Main entry point of the program. + * + * @param args + * the arguments passed to the {@link TestLauncher}s. + */ + static public void main(String[] args) { + System.exit(new Test(args).launch()); + } +} diff --git a/src/be/nikiroo/utils/test/TestCase.java b/src/be/nikiroo/utils/test/TestCase.java new file mode 100644 index 00000000..0349bc07 --- /dev/null +++ b/src/be/nikiroo/utils/test/TestCase.java @@ -0,0 +1,238 @@ +package be.nikiroo.utils.test; + +/** + * A {@link TestCase} that can be run with {@link TestLauncher}. + * + * @author niki + */ +abstract public class TestCase { + /** + * The type of {@link Exception} used to signal a failed assertion or a + * force-fail. + * + * @author niki + */ + class AssertException extends Exception { + private static final long serialVersionUID = 1L; + + public AssertException(String reason) { + super(reason); + } + } + + private String name; + + /** + * Create a new {@link TestCase}. + * + * @param name + * the test name + */ + public TestCase(String name) { + this.name = name; + } + + /** + * Setup the test (called before the test is run). + * + * @throws Exception + * in case of error + */ + public void setUp() throws Exception { + } + + /** + * Tear-down the test (called when the test has been ran). + * + * @throws Exception + * in case of error + */ + public void tearDown() throws Exception { + } + + /** + * The test name. + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Actually do the test. + * + * @throws Exception + * in case of error + */ + abstract public void test() throws Exception; + + /** + * Force a failure. + * + * @throws AssertException + * every time + */ + public void fail() throws AssertException { + fail(null); + } + + /** + * Force a failure. + * + * @param reason + * the failure reason + * @throws AssertException + * every time + */ + public void fail(String reason) throws AssertException { + throw new AssertException("Failed!" + // + reason != null ? "\n" + reason : ""); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(Object expected, Object actual) + throws AssertException { + assertEquals(null, expected, actual); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param the + * error message to display if they differ + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(String errorMessage, Object expected, Object actual) + throws AssertException { + + if (errorMessage == null) { + errorMessage = String.format("" // + + "Assertion failed!\n" // + + "Expected value: [%s]\n" // + + "Actual value: [%s]", expected, actual); + } + + if ((expected == null && actual != null) + || (expected != null && !expected.equals(actual))) { + throw new AssertException(errorMessage); + } + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(long expected, long actual) throws AssertException { + assertEquals(new Long(expected), new Long(actual)); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param the + * error message to display if they differ + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(String errorMessage, long expected, long actual) + throws AssertException { + assertEquals(errorMessage, new Long(expected), new Long(actual)); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(boolean expected, boolean actual) + throws AssertException { + assertEquals(new Boolean(expected), new Boolean(actual)); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param the + * error message to display if they differ + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(String errorMessage, boolean expected, + boolean actual) throws AssertException { + assertEquals(errorMessage, new Boolean(expected), new Boolean(actual)); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(double expected, double actual) + throws AssertException { + assertEquals(new Double(expected), new Double(actual)); + } + + /** + * Check that 2 {@link Object}s are equals. + * + * @param the + * error message to display if they differ + * @param expected + * the expected value + * @param actual + * the actual value + * + * @throws AssertException + * in case they differ + */ + public void assertEquals(String errorMessage, double expected, double actual) + throws AssertException { + assertEquals(errorMessage, new Double(expected), new Double(actual)); + } +} diff --git a/src/be/nikiroo/utils/test/TestLauncher.java b/src/be/nikiroo/utils/test/TestLauncher.java new file mode 100644 index 00000000..ba4e81de --- /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(); + } +} diff --git a/src/be/nikiroo/utils/test/bundle_test.properties b/src/be/nikiroo/utils/test/bundle_test.properties new file mode 100644 index 00000000..5222c59e --- /dev/null +++ b/src/be/nikiroo/utils/test/bundle_test.properties @@ -0,0 +1,3 @@ +ONE = un +ONE_SUFFIX = un + suffix +JAPANESE = 日本語 Nihongo \ No newline at end of file -- 2.27.0