Add framework for semantic CI tests
diff --git a/test/assert.sh b/test/assert.sh
new file mode 100644
index 0000000..acceebf
--- /dev/null
+++ b/test/assert.sh
@@ -0,0 +1,248 @@
+#!/usr/bin/env bash
+
+#####################################################################
+##
+## title: Assert Extension
+##
+## description:
+## Assert extension of shell (bash, ...)
+## with the common assert functions
+## Function list based on:
+## http://junit.sourceforge.net/javadoc/org/junit/Assert.html
+## Log methods : inspired by
+## - https://natelandau.com/bash-scripting-utilities/
+## author: Mark Torok
+##
+## date: 07. Dec. 2016
+##
+## license: MIT
+##
+#####################################################################
+
+if command -v tput &>/dev/null && tty -s; then
+ RED=$(tput setaf 1)
+ GREEN=$(tput setaf 2)
+ MAGENTA=$(tput setaf 5)
+ NORMAL=$(tput sgr0)
+ BOLD=$(tput bold)
+else
+ RED=$(echo -en "\e[31m")
+ GREEN=$(echo -en "\e[32m")
+ MAGENTA=$(echo -en "\e[35m")
+ NORMAL=$(echo -en "\e[00m")
+ BOLD=$(echo -en "\e[01m")
+fi
+
+log_header() {
+ printf "\n${BOLD}${MAGENTA}========== %s ==========${NORMAL}\n" "$@" >&2
+}
+
+log_success() {
+ printf "${GREEN}✔ %s${NORMAL}\n" "$@" >&2
+}
+
+log_failure() {
+ printf "${RED}✖ %s${NORMAL}\n" "$@" >&2
+}
+
+
+assert_eq() {
+ local expected="$1"
+ local actual="$2"
+ local msg="${3-}"
+
+ if [ "$expected" == "$actual" ]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$expected == $actual :: $msg" || true
+ return 1
+ fi
+}
+
+assert_not_eq() {
+ local expected="$1"
+ local actual="$2"
+ local msg="${3-}"
+
+ if [ ! "$expected" == "$actual" ]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$expected != $actual :: $msg" || true
+ return 1
+ fi
+}
+
+assert_true() {
+ local actual="$1"
+ local msg="${3-}"
+
+ assert_eq true "$actual" "$msg"
+ return "$?"
+}
+
+assert_false() {
+ local actual="$1"
+ local msg="${3-}"
+
+ assert_eq false "$actual" "$msg"
+ return "$?"
+}
+
+assert_array_eq() {
+
+ declare -a expected=("${!1-}")
+ # echo "AAE ${expected[@]}"
+
+ declare -a actual=("${!2}")
+ # echo "AAE ${actual[@]}"
+
+ local msg="${3-}"
+
+ local return_code=0
+ if [ ! "${#expected[@]}" == "${#actual[@]}" ]; then
+ return_code=1
+ fi
+
+ local i
+ for (( i=1; i < ${#expected[@]} + 1; i+=1 )); do
+ if [ ! "${expected[$i-1]}" == "${actual[$i-1]}" ]; then
+ return_code=1
+ break
+ fi
+ done
+
+ if [ "$return_code" == 1 ]; then
+ [ "${#msg}" -gt 0 ] && log_failure "(${expected[*]}) != (${actual[*]}) :: $msg" || true
+ fi
+
+ return "$return_code"
+}
+
+assert_array_not_eq() {
+
+ declare -a expected=("${!1-}")
+ declare -a actual=("${!2}")
+
+ local msg="${3-}"
+
+ local return_code=1
+ if [ ! "${#expected[@]}" == "${#actual[@]}" ]; then
+ return_code=0
+ fi
+
+ local i
+ for (( i=1; i < ${#expected[@]} + 1; i+=1 )); do
+ if [ ! "${expected[$i-1]}" == "${actual[$i-1]}" ]; then
+ return_code=0
+ break
+ fi
+ done
+
+ if [ "$return_code" == 1 ]; then
+ [ "${#msg}" -gt 0 ] && log_failure "(${expected[*]}) == (${actual[*]}) :: $msg" || true
+ fi
+
+ return "$return_code"
+}
+
+assert_empty() {
+ local actual=$1
+ local msg="${2-}"
+
+ assert_eq "" "$actual" "$msg"
+ return "$?"
+}
+
+assert_not_empty() {
+ local actual=$1
+ local msg="${2-}"
+
+ assert_not_eq "" "$actual" "$msg"
+ return "$?"
+}
+
+assert_contain() {
+ local haystack="$1"
+ local needle="${2-}"
+ local msg="${3-}"
+
+ if [ -z "${needle:+x}" ]; then
+ return 0;
+ fi
+
+ if [ -z "${haystack##*$needle*}" ]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$haystack doesn't contain $needle :: $msg" || true
+ return 1
+ fi
+}
+
+assert_not_contain() {
+ local haystack="$1"
+ local needle="${2-}"
+ local msg="${3-}"
+
+ if [ -z "${needle:+x}" ]; then
+ return 0;
+ fi
+
+ if [ "${haystack##*$needle*}" ]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$haystack contains $needle :: $msg" || true
+ return 1
+ fi
+}
+
+assert_gt() {
+ local first="$1"
+ local second="$2"
+ local msg="${3-}"
+
+ if [[ "$first" -gt "$second" ]]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$first > $second :: $msg" || true
+ return 1
+ fi
+}
+
+assert_ge() {
+ local first="$1"
+ local second="$2"
+ local msg="${3-}"
+
+ if [[ "$first" -ge "$second" ]]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$first >= $second :: $msg" || true
+ return 1
+ fi
+}
+
+assert_lt() {
+ local first="$1"
+ local second="$2"
+ local msg="${3-}"
+
+ if [[ "$first" -lt "$second" ]]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$first < $second :: $msg" || true
+ return 1
+ fi
+}
+
+assert_le() {
+ local first="$1"
+ local second="$2"
+ local msg="${3-}"
+
+ if [[ "$first" -le "$second" ]]; then
+ return 0
+ else
+ [ "${#msg}" -gt 0 ] && log_failure "$first <= $second :: $msg" || true
+ return 1
+ fi
+}
diff --git a/test/test-xml.sh b/test/test-xml.sh
new file mode 100755
index 0000000..65ade92
--- /dev/null
+++ b/test/test-xml.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env bash
+TESTDIR=$(dirname $0)
+ASSERTSH=${TESTDIR}/assert.sh
+set -e
+. ${ASSERTSH}
+
+TEXTS=6
+I5_FILE=target/dnb18.i5.xml
+if [ ! -f "$I5_FILE" ]; then
+ log_failure "File $I5_FILE does not exist"
+ exit 1
+fi
+
+
+observed=$(xmlstarlet sel --net -t -v "count(//idsText)" $I5_FILE)
+
+if $(assert_eq "$observed" "$TEXTS"); then
+ log_success "$I5_FILE contains $TEXTS idsText elements"
+else
+ log_failure "$I5_FILE does not contain $TEXTS idsText elements"
+fi
+
+
+observed=$(xmlstarlet sel --net -t -v "count(/idsCorpus/idsDoc/idsText/idsHeader/fileDesc/sourceDesc/biblStruct/monogr/h.author[normalize-space(.)])" $I5_FILE)
+if $(assert_eq "$observed" "$TEXTS"); then
+ log_success "$I5_FILE contains $TEXTS non-empty h.author elements"
+else
+ log_failure "$I5_FILE does not contain $TEXTS non-empty h.author elements"
+fi
+
+observed=$(xmlstarlet sel --net -t -v "count(/idsCorpus/idsDoc/idsText/idsHeader/fileDesc/sourceDesc/biblStruct/monogr/h.author[contains(., '[')])" $I5_FILE)
+if $(assert_eq "$observed" "0"); then
+ log_success "authors do not contain []"
+else
+ log_failure "authors contain []"
+fi
\ No newline at end of file