#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/
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
$(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...
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" ; \
@[ "$(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
[ $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
* resource file)
*/
public String getString(E id) {
- return getStringX(id, "");
+ return getStringX(id, null);
}
/**
*/
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();
}
/**
- * 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.
+ * <p>
+ * 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
writer.close();
}
+ /**
+ * Reload the {@link Bundle} data files.
+ */
+ public void reload() {
+ setBundle(name, null);
+ }
+
/**
* Check if the internal map contains the given key.
*
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) {
setBundle(name, locale);
}
+ @Override
+ public void reload() {
+ setBundle(name, locale);
+ }
+
@Override
public String getString(E id) {
return getString(id, (Object[]) null);
--- /dev/null
+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<E> {
+ 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
+ }
+}
--- /dev/null
+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());
+ }
+}
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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<TestLauncher> series;
+ private List<TestCase> 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<TestLauncher>();
+ tests = new ArrayList<TestCase>();
+ 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();
+ }
+}
--- /dev/null
+ONE = un
+ONE_SUFFIX = un + suffix
+JAPANESE = 日本語 Nihongo
\ No newline at end of file