diff mbox series

[RFC,1/1] Add automated tests for shell lib

Message ID c859d712cb33f6bb151313d6664d786920e49500.1535725022.git.clanig@suse.com
State Changes Requested
Headers show
Series Add automated tests for shell lib | expand

Commit Message

Christian Lanig Aug. 31, 2018, 3:24 p.m. UTC
Signed-off-by: Christian Lanig <clanig@suse.com>
---
 doc/write-tests-for-shell-lib.txt                  |  61 ++++++++++++
 .../shell/test.TST_TEST.TST_CNT.separate.sh        |  29 ++++++
 lib/newlib_tests/shell/test.TST_TEST.TST_CNT.sh    |  27 ++++++
 lib/newlib_tests/shell/test.TST_TEST.getopts.sh    |  48 ++++++++++
 lib/newlib_tests/shell/test.TST_TEST.sh            |  32 +++++++
 .../shell/test.TST_TEST_DATA.TST_CNT.separate.sh   |  32 +++++++
 .../shell/test.TST_TEST_DATA.TST_CNT.sh            |  30 ++++++
 .../shell/test.TST_TEST_DATA.getopts.sh            |  50 ++++++++++
 lib/newlib_tests/shell/test.TST_TEST_DATA.sh       |  24 +++++
 .../shell/test.TST_TEST_DATA_IFS.getopts.sh        |  52 ++++++++++
 lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.sh   |  27 ++++++
 lib/newlib_tests/test_sh_newlib.sh                 | 105 +++++++++++++++++++++
 12 files changed, 517 insertions(+)
 create mode 100644 doc/write-tests-for-shell-lib.txt
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST.TST_CNT.separate.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST.TST_CNT.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST.getopts.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.separate.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA.getopts.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.getopts.sh
 create mode 100755 lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.sh
 create mode 100755 lib/newlib_tests/test_sh_newlib.sh

Comments

Cyril Hrubis Oct. 3, 2018, 9:51 a.m. UTC | #1
Hi!
> diff --git a/lib/newlib_tests/shell/test.TST_TEST.getopts.sh b/lib/newlib_tests/shell/test.TST_TEST.getopts.sh
> new file mode 100755
> index 000000000..65810b5e8
> --- /dev/null
> +++ b/lib/newlib_tests/shell/test.TST_TEST.getopts.sh
> @@ -0,0 +1,48 @@
> +#!/bin/sh
> +#
> +# Optional test command line parameters
> +#
> +
> +TST_OPTS="af:"
> +TST_USAGE=usage
> +TST_PARSE_ARGS=parse_args
> +TST_TESTFUNC=do_test
> +
> +. tst_test.sh
> +
> +ALTERNATIVE=0
> +MODE="foo"
> +
> +usage()
> +{
> +	cat << EOF
> +usage: $0 [-a] [-f <foo|bar>]
> +
> +OPTIONS
> +-a     Enable support for alternative foo
> +-f     Specify foo or bar mode
> +EOF
> +}
> +
> +parse_args()
> +{
> +	case $1 in
> +	a) ALTERNATIVE=1;;
> +	f) MODE="$2";;
> +	esac
> +}
> +
> +do_test()
> +{
> +	tst_res TPASS "Test $1 passed with data '$2': a: '$ALTERNATIVE', f: '$MODE'"
> +}
> +
> +tst_run
> +# output:
> +# test 1 TPASS: Test 1 passed with data '': a: '0', f: 'foo'
> +# 
> +# Summary:
> +# passed   1
> +# failed   0
> +# skipped  0
> +# warnings 0

We do need a way how to pass parameters to the tests so that we can test
this functionality properly (and also the default options that are
implemented in the test library).

Obvious way how to do this is to allow to have several output: blocks at
the end of the test with a different parameters, something as:

# output:
# test 1 TPASS: Test 1 passed with data '': a: '0', f: 'foo'
# 
# Summary:
# passed   1
# failed   0
# skipped  0
# warnings 0

# output:
# params: -a
# test 1 TPASS: Test 1 passed with data '': a: '1', f: 'foo'
# 
# Summary:
# passed   1
# failed   0
# skipped  0
# warnings 0

...

> diff --git a/lib/newlib_tests/test_sh_newlib.sh b/lib/newlib_tests/test_sh_newlib.sh
> new file mode 100755
> index 000000000..bf335dd6e
> --- /dev/null
> +++ b/lib/newlib_tests/test_sh_newlib.sh
> @@ -0,0 +1,105 @@
> +#!/bin/sh
> +#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +# ?? 2018 SUSE LLC.
> +# 
> +# Author: Christian Lanig <clanig@suse.com>
> +
> +setup()
> +{
> +	path_sav=$PATH

There is no need to save and restore the PATH, the script will be
executed in subshell and the changes to variables are never propagated
to the parent shell.

The only way how variables can be changed in the currently running shell
is to source the script, in that case the current shell will execute the
commands.

> +	PATH=""$PATH":$(dirname $0)/../../testcases/lib/"
> +
> +	color_blue='\033[1;34m'
> +	color_green='\033[1;32m'
> +	color_red='\033[1;31m'
> +	standard_color='\033[0m'
          ^
	  This is more of 'reset text attributes' as it not only changes
	  the color to default but also turns off bold, underscore,
	  blink, etc. So it would be better named something as
	  reset_attrs='\033[0m'

> +	tmp_dir="/tmp/sh_lib_tst-$$/"
> +	mkdir $tmp_dir || cleanup 1
> +	sh_lib_tst_dir="$(dirname $0)/shell/"
> +	tst_files="$(ls $sh_lib_tst_dir)"
> +	return 0
> +}
> +
> +cleanup()
> +{
> +	[ -d "$tmp_dir" ] && rm -rf "$tmp_dir"
> +	PATH="$PATH_sav"
> +	exit $1
> +}
> +
> +print_help()
> +{
> +	printf "#############################################################\n"
> +	printf "# This script iterates over test cases for the new shell    #\n"
> +	printf "# library and verifies the output.                          #\n"
> +	printf "#############################################################\n"
> +	printf "\n"
> +	printf "Usage:\n"
> +	printf "\t$(basename $0)\n"
> +	printf "\t$(basename $0) [test file 1] [test file 2] ...\n\n"
> +	exit 0;
> +}
> +
> +parse_params()
> +{
> +	[ -n "$1" ] && tst_files=
> +	while [ -n "$1" ]; do
> +		case "$1" in
> +			--help) print_help;;
> +			-h) print_help;;
> +			-*)
> +				printf "Unknown positional parameter "$1".\n"
> +				cleanup 1;;
> +			*) tst_files="$tst_files $1";;
> +		esac
> +		shift
> +	done
> +	return 0
> +}
> +
> +verify_output()
> +{
> +	[ -e "$sh_lib_tst_dir$tst" ] || { printf "$tst not found\n" && \
> +			cleanup 1 ;}
> +	# read all lines after line: `# output:`, and strip `# ` from beginning
> +	sed -n -e '/^# output:/,$ p;' "$sh_lib_tst_dir$tst" | sed '1d; s/^# //'\
> +			> "$tmp_dir$tst.wanted" || cleanup 1

Usually doing more complex text manipulation is easier with awk

In this case you can use gsub() function that will replace substring and
print all lines that were replaced (i.e. print all comments without the
hash and space):

awk 'gsub("^# ", "", $0)' script.sh

Then you can even get one of the output: blocks with:

awk -vb=1 'gsub("^# ", "", $0) {if (/output:/) {p++}; if (p==b && !/output:/) {print $0}};' script.sh
       ^
       This gives you content of comments starting at first # output:
       line up to the next # output: or end of line, which is exactly
       what we need if we want to support passing parameters to
       testcases. Then we can simply count how many # output: lines are
       in a file and then run this command in a loop extracting one
       example output after another.

> +	actual_output=$($sh_lib_tst_dir$tst)
> +	# remove control signs, add newline at EOF and store in temporary file
> +	actual_output=$(printf "$actual_output\n" > $tmp_dir$tst".actual") ||
> +			cleanup 1
> +
> +	cmp $tmp_dir$tst".actual" $tmp_dir$tst".wanted" > /dev/null && return 0
> +	return 1
> +}
> +
> +run_tests()
> +{
> +	pass_cnt=0
> +	fail_cnt=0
> +	printf "\n"
> +	for tst in $tst_files; do
> +		if verify_output; then
> +			pass_cnt=$(expr $pass_cnt + 1)

You can use arithmetic expansion here, i.e. pass_cnt=$((pass_cnt+1))

> +			printf ""$color_green"TPASS$standard_color $tst"
> +			printf "\n"
> +		else
> +			fail_cnt=$(expr $fail_cnt + 1)

And here as well.

> +			printf ""$color_red"TFAIL$standard_color $tst\n"
> +			printf ""$color_blue"Diff:$standard_color\n"
> +			diff -u "$tmp_dir$tst.actual" "$tmp_dir$tst.wanted"
> +			printf "\n"
> +		fi
> +	done
> +	printf "\nSummary:\n"
> +	printf ""$color_red"Failed:$standard_color $fail_cnt\n"
> +	printf ""$color_green"Passed:$standard_color $pass_cnt\n\n"
> +	return $fail_cnt
> +}
> +
> +setup
> +parse_params "$@"
> +run_tests
> +res=$?
> +cleanup $res
> -- 
> 2.16.4
>
Petr Vorel Oct. 3, 2018, 10:46 a.m. UTC | #2
Hi,

> > +do_test()
> > +{
> > +	tst_res TPASS "Test $1 passed with data '$2': a: '$ALTERNATIVE', f: '$MODE'"
> > +}
> > +
> > +tst_run
> > +# output:
> > +# test 1 TPASS: Test 1 passed with data '': a: '0', f: 'foo'
> > +# 
> > +# Summary:
> > +# passed   1
> > +# failed   0
> > +# skipped  0
> > +# warnings 0

> We do need a way how to pass parameters to the tests so that we can test
> this functionality properly (and also the default options that are
> implemented in the test library).

> Obvious way how to do this is to allow to have several output: blocks at
> the end of the test with a different parameters, something as:

> # output:
> # test 1 TPASS: Test 1 passed with data '': a: '0', f: 'foo'

> # Summary:
> # passed   1
> # failed   0
> # skipped  0
> # warnings 0

> # output:
> # params: -a
> # test 1 TPASS: Test 1 passed with data '': a: '1', f: 'foo'

> # Summary:
> # passed   1
> # failed   0
> # skipped  0
> # warnings 0

> ...

Good idea!

...
> > +verify_output()
> > +{
> > +	[ -e "$sh_lib_tst_dir$tst" ] || { printf "$tst not found\n" && \
> > +			cleanup 1 ;}
> > +	# read all lines after line: `# output:`, and strip `# ` from beginning
> > +	sed -n -e '/^# output:/,$ p;' "$sh_lib_tst_dir$tst" | sed '1d; s/^# //'\
> > +			> "$tmp_dir$tst.wanted" || cleanup 1

> Usually doing more complex text manipulation is easier with awk

> In this case you can use gsub() function that will replace substring and
> print all lines that were replaced (i.e. print all comments without the
> hash and space):

> awk 'gsub("^# ", "", $0)' script.sh

> Then you can even get one of the output: blocks with:

> awk -vb=1 'gsub("^# ", "", $0) {if (/output:/) {p++}; if (p==b && !/output:/) {print $0}};' script.sh
>        ^
>        This gives you content of comments starting at first # output:
>        line up to the next # output: or end of line, which is exactly
>        what we need if we want to support passing parameters to
>        testcases. Then we can simply count how many # output: lines are
>        in a file and then run this command in a loop extracting one
>        example output after another.
I wonder whether "extracting one example output after another" is still good to
do in awk/sed/shell (with simple readable code). Probably yes, but I wouldn't
mind to have either python (pytest) or some perl code doing it.
Awk is everywhere and definitely more capable than sed or even shell, but not
everybody is fluent in it.


Kind regards,
Petr
Petr Vorel Oct. 3, 2018, 11:32 a.m. UTC | #3
Hi Christian,

some more comments (beside these mentioned by Cyril).
Mostly just shell code style comments.

> +test2()
> +{
> +	tst_res TPASS "Test $1 passed with no data ('$2')"
> +}
> +
> +tst_run
> +# output:
> +# test 1 TPASS: Test 1 passed with no data ('')
> +# test 2 TPASS: Test 2 passed with no data ('')
> +# 
Note, I'd prefer not have trailing whitespace after '#' (actually anywhere).
Not only it's unnecessary it's also problematic for 'git am' applying patches.
In my case all the tests failed because of it.
Also some people setup their editors / IDE to delete trailing whitespace.
(Not pointing all whitespace here.)

> +# Summary:
> +# passed   2
> +# failed   0
> +# skipped  0
> +# warnings 0

> diff --git a/lib/newlib_tests/test_sh_newlib.sh b/lib/newlib_tests/test_sh_newlib.sh
...
> +setup()
> +{
> +	path_sav=$PATH
> +	PATH=""$PATH":$(dirname $0)/../../testcases/lib/"
> +	color_blue='\033[1;34m'
> +	color_green='\033[1;32m'
> +	color_red='\033[1;31m'
> +	standard_color='\033[0m'

Maybe this variables can be outside of setup (no benefit to have them in setup.

> +	tmp_dir="/tmp/sh_lib_tst-$$/"

Although /tmp is everywhere, maybe using $TMPDIR would be good. See tst_test.sh
code:
		if [ -z "$TMPDIR" ]; then
			export TMPDIR="/tmp"
		fi

		TST_TMPDIR=$(mktemp -d "$TMPDIR/LTP_$TST_ID.XXXXXXXXXX")

		chmod 777 "$TST_TMPDIR"

> +	mkdir $tmp_dir || cleanup 1
> +	sh_lib_tst_dir="$(dirname $0)/shell/"
Maybe just tstdir ?
> +	tst_files="$(ls $sh_lib_tst_dir)"

> +print_help()
> +{
> +	printf "#############################################################\n"
> +	printf "# This script iterates over test cases for the new shell    #\n"
> +	printf "# library and verifies the output.                          #\n"
> +	printf "#############################################################\n"
> +	printf "\n"
> +	printf "Usage:\n"
> +	printf "\t$(basename $0)\n"
> +	printf "\t$(basename $0) [test file 1] [test file 2] ...\n\n"
> +	exit 0;

Maybe just:
    cat <<EOF
Unit tests for new shell API
Usage: $(basename $0) [ TEST1 ] [ TEST2 ] 
EOF

> +}
> +
> +parse_params()
> +{
> +	[ -n "$1" ] && tst_files=
> +	while [ -n "$1" ]; do
> +		case "$1" in
> +			--help) print_help;;
> +			-h) print_help;;
> +			-*)
> +				printf "Unknown positional parameter "$1".\n"
> +				cleanup 1;;
> +			*) tst_files="$tst_files $1";;
> +		esac
> +		shift
> +	done
> +	return 0
Why return, when you don't use it elsewhere?

...
> +verify_output()
> +{
> +	[ -e "$sh_lib_tst_dir$tst" ] || { printf "$tst not found\n" && \
> +			cleanup 1 ;}
This might be more readable (nitpicking):
if [ -n "$sh_lib_tst_dir$tst" ]; then
	echo "$tst not found"
	cleanup 1
fi

> +	# read all lines after line: `# output:`, and strip `# ` from beginning
> +	sed -n -e '/^# output:/,$ p;' "$sh_lib_tst_dir$tst" | sed '1d; s/^# //'\
> +			> "$tmp_dir$tst.wanted" || cleanup 1
> +
> +	actual_output=$($sh_lib_tst_dir$tst)

> +	# remove control signs, add newline at EOF and store in temporary file
> +	actual_output=$(printf "$actual_output\n" > $tmp_dir$tst".actual") ||
> +			cleanup 1
Suppose this part will be rewritten. But why do you assign empty string?
echo "$actual_output" > $tmp_dir$tst".actual || cleanup 1

...
> +
> +	cmp $tmp_dir$tst".actual" $tmp_dir$tst".wanted" > /dev/null && return 0
> +	return 1
This is enough (cmp returns it for you):
cmp -s "$tmp_dir$tst.actual" "$tmp_dir$tst.wanted"
> +}

...
> +run_tests()
> +{
> +	pass_cnt=0
> +	fail_cnt=0
> +	printf "\n"
> +	for tst in $tst_files; do
> +		if verify_output; then
> +			pass_cnt=$(expr $pass_cnt + 1)
> +			printf ""$color_green"TPASS$standard_color $tst"
                   ^
"" is empty string, why?
> +			printf "\n"
You can also use {} to separate variable name:
echo "${color_green}TPASS$standard_color $tst"
echo adds new line for you (for simple usage it's portable).
It's safer to quote whole string (with variables).

> +		else
> +			fail_cnt=$(expr $fail_cnt + 1)
> +			printf ""$color_red"TFAIL$standard_color $tst\n"
> +			printf ""$color_blue"Diff:$standard_color\n"
> +			diff -u "$tmp_dir$tst.actual" "$tmp_dir$tst.wanted"
> +			printf "\n"
> +		fi
> +	done
> +	printf "\nSummary:\n"
> +	printf ""$color_red"Failed:$standard_color $fail_cnt\n"
> +	printf ""$color_green"Passed:$standard_color $pass_cnt\n\n"

And how about adding helper functions print_red, print_green, print_blue?
Maybe not worth for this little script.

print_red()
{
	printf "$red$1$reset"
}
print_red "TFAIL
echo "$tst"

> +	return $fail_cnt
> +}
> +
> +setup
> +parse_params "$@"

> +run_tests
> +res=$?
> +cleanup $res
No need to have $res:
run_tests
cleanup $?


Kind regards,
Petr
diff mbox series

Patch

diff --git a/doc/write-tests-for-shell-lib.txt b/doc/write-tests-for-shell-lib.txt
new file mode 100644
index 000000000..6f3ff4854
--- /dev/null
+++ b/doc/write-tests-for-shell-lib.txt
@@ -0,0 +1,61 @@ 
+How to format tests in order to test the shell library
+======================================================
+
+It is of big importance not only to test the Linux kernel functionality but also
+to make sure LTP is running correctly itself. For this reason it is useful to
+test intrinsic functionality of LTP - to reasonable extend.
+
+1. Running tests for the shell library
+--------------------------------------
+The test cases reside in the folder `lib/newlib_tests/shell`. A script executing
+them one by one is in the folder `lib/newlib_tests`. You can execute this script
+to test all cases or specify test cases to be run. The script is called
+`test_sh_newlib.sh`.
+
+2. Writing tests for the shell library
+--------------------------------------
+The tests are written like all other test cases using the shell library.
+Additionally, at the end of the file the desired output is added. As an example:
+
+[source,shell]
+-------------------------------------------------------------------------------
+#!/bin/sh
+#
+# This is a basic test for true shell buildin
+#
+
+TST_TESTFUNC=do_test
+. tst_test.sh
+
+do_test()
+{
+	true
+	ret=$?
+
+	tst_res TINFO "Test $1 passed with no data ('$2')"
+
+	if [ $ret -eq 0 ]; then
+		tst_res TPASS "true returned 0"
+	else
+		tst_res TFAIL "true returned $ret"
+	fi
+}
+
+tst_run
+# output:
+# test 1 TINFO: Test 1 passed with no data ('')
+# test 1 TPASS: true returned 0
+# 
+# Summary:
+# passed   1
+# failed   0
+# skipped  0
+# warnings 0
+-------------------------------------------------------------------------------
+
+The most noticeable thing is to add the line `# output:` to show the parser that
+parsing should start in the following line. For the following lines the `# `
+will be stripped before the output is then compared with the actual output that
+gets printed on the terminal when running the test. Also make sure that there
+are no trailing lines at the end of the document. But make sure to add one
+trailing space after the `#` when the line will be actually empty.
diff --git a/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.separate.sh b/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.separate.sh
new file mode 100755
index 000000000..5e8d4bfb5
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.separate.sh
@@ -0,0 +1,29 @@ 
+#!/bin/sh
+#
+# Example test with tests in separate functions
+#
+
+TST_TESTFUNC=test
+TST_CNT=2
+. tst_test.sh
+
+test1()
+{
+	tst_res TPASS "Test $1 passed with no data ('$2')"
+}
+
+test2()
+{
+	tst_res TPASS "Test $1 passed with no data ('$2')"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with no data ('')
+# test 2 TPASS: Test 2 passed with no data ('')
+# 
+# Summary:
+# passed   2
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.sh b/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.sh
new file mode 100755
index 000000000..62fa652cf
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST.TST_CNT.sh
@@ -0,0 +1,27 @@ 
+#!/bin/sh
+#
+# Example test with tests in a single function
+#
+
+TST_TESTFUNC=do_test
+TST_CNT=2
+. tst_test.sh
+
+do_test()
+{
+	case $1 in
+	1) tst_res TPASS "Test $1 passed with no data ('$2')";;
+	2) tst_res TPASS "Test $1 passed with no data ('$2')";;
+	esac
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with no data ('')
+# test 2 TPASS: Test 2 passed with no data ('')
+# 
+# Summary:
+# passed   2
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST.getopts.sh b/lib/newlib_tests/shell/test.TST_TEST.getopts.sh
new file mode 100755
index 000000000..65810b5e8
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST.getopts.sh
@@ -0,0 +1,48 @@ 
+#!/bin/sh
+#
+# Optional test command line parameters
+#
+
+TST_OPTS="af:"
+TST_USAGE=usage
+TST_PARSE_ARGS=parse_args
+TST_TESTFUNC=do_test
+
+. tst_test.sh
+
+ALTERNATIVE=0
+MODE="foo"
+
+usage()
+{
+	cat << EOF
+usage: $0 [-a] [-f <foo|bar>]
+
+OPTIONS
+-a     Enable support for alternative foo
+-f     Specify foo or bar mode
+EOF
+}
+
+parse_args()
+{
+	case $1 in
+	a) ALTERNATIVE=1;;
+	f) MODE="$2";;
+	esac
+}
+
+do_test()
+{
+	tst_res TPASS "Test $1 passed with data '$2': a: '$ALTERNATIVE', f: '$MODE'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data '': a: '0', f: 'foo'
+# 
+# Summary:
+# passed   1
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST.sh b/lib/newlib_tests/shell/test.TST_TEST.sh
new file mode 100755
index 000000000..fa6aaa57f
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST.sh
@@ -0,0 +1,32 @@ 
+#!/bin/sh
+#
+# This is a basic test for true shell buildin
+#
+
+TST_TESTFUNC=do_test
+. tst_test.sh
+
+do_test()
+{
+	true
+	ret=$?
+
+	tst_res TINFO "Test $1 passed with no data ('$2')"
+
+	if [ $ret -eq 0 ]; then
+		tst_res TPASS "true returned 0"
+	else
+		tst_res TFAIL "true returned $ret"
+	fi
+}
+
+tst_run
+# output:
+# test 1 TINFO: Test 1 passed with no data ('')
+# test 1 TPASS: true returned 0
+# 
+# Summary:
+# passed   1
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.separate.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.separate.sh
new file mode 100755
index 000000000..0ae773519
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.separate.sh
@@ -0,0 +1,32 @@ 
+#!/bin/sh
+#
+# Example test with tests in separate functions, using $TST_TEST_DATA and $TST_CNT
+#
+
+TST_TESTFUNC=test
+TST_CNT=2
+TST_TEST_DATA="foo:bar:d dd"
+. tst_test.sh
+
+test1()
+{
+	tst_res TPASS "Test $1 passed with data '$2'"
+}
+
+test2()
+{
+	tst_res TPASS "Test $1 passed with data '$2'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo:bar:d'
+# test 2 TPASS: Test 2 passed with data 'foo:bar:d'
+# test 3 TPASS: Test 1 passed with data 'dd'
+# test 4 TPASS: Test 2 passed with data 'dd'
+# 
+# Summary:
+# passed   4
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.sh
new file mode 100755
index 000000000..71e6acac7
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA.TST_CNT.sh
@@ -0,0 +1,30 @@ 
+#!/bin/sh
+#
+# Example test with tests in a single function, using $TST_TEST_DATA and $TST_CNT
+#
+
+TST_TESTFUNC=do_test
+TST_CNT=2
+TST_TEST_DATA="foo:bar:d dd"
+. tst_test.sh
+
+do_test()
+{
+	case $1 in
+	1) tst_res TPASS "Test $1 passed with data '$2'";;
+	2) tst_res TPASS "Test $1 passed with data '$2'";;
+	esac
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo:bar:d'
+# test 2 TPASS: Test 2 passed with data 'foo:bar:d'
+# test 3 TPASS: Test 1 passed with data 'dd'
+# test 4 TPASS: Test 2 passed with data 'dd'
+# 
+# Summary:
+# passed   4
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA.getopts.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA.getopts.sh
new file mode 100755
index 000000000..e1f18ba3a
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA.getopts.sh
@@ -0,0 +1,50 @@ 
+#!/bin/sh
+#
+# Optional test command line parameters
+#
+
+TST_OPTS="af:"
+TST_USAGE=usage
+TST_PARSE_ARGS=parse_args
+TST_TESTFUNC=do_test
+TST_TEST_DATA="foo0:bar:d dd"
+
+. tst_test.sh
+
+ALTERNATIVE=0
+MODE="foo"
+
+usage()
+{
+	cat << EOF
+usage: $0 [-a] [-f <foo|bar>]
+
+OPTIONS
+-a     Enable support for alternative foo
+-f     Specify foo or bar mode
+EOF
+}
+
+parse_args()
+{
+	case $1 in
+	a) ALTERNATIVE=1;;
+	f) MODE="$2";;
+	esac
+}
+
+do_test()
+{
+	tst_res TPASS "Test $1 passed with data '$2': a: '$ALTERNATIVE', f: '$MODE'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo0:bar:d': a: '0', f: 'foo'
+# test 2 TPASS: Test 1 passed with data 'dd': a: '0', f: 'foo'
+# 
+# Summary:
+# passed   2
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA.sh
new file mode 100755
index 000000000..d3ced1954
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA.sh
@@ -0,0 +1,24 @@ 
+#!/bin/sh
+#
+# Example test with tests in a single function, using $TST_TEST_DATA
+#
+
+TST_TESTFUNC=do_test
+TST_TEST_DATA="foo:bar:d dd"
+. tst_test.sh
+
+do_test()
+{
+	tst_res TPASS "Test $1 passed with data '$2'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo:bar:d'
+# test 2 TPASS: Test 1 passed with data 'dd'
+# 
+# Summary:
+# passed   2
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.getopts.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.getopts.sh
new file mode 100755
index 000000000..6075904a5
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.getopts.sh
@@ -0,0 +1,52 @@ 
+#!/bin/sh
+#
+# Optional test command line parameters
+#
+
+TST_OPTS="af:"
+TST_USAGE=usage
+TST_PARSE_ARGS=parse_args
+TST_TESTFUNC=do_test
+TST_TEST_DATA="foo0:bar:d dd"
+TST_TEST_DATA_IFS=":"
+
+. tst_test.sh
+
+ALTERNATIVE=0
+MODE="foo"
+
+usage()
+{
+	cat << EOF
+usage: $0 [-a] [-f <foo|bar>]
+
+OPTIONS
+-a     Enable support for alternative foo
+-f     Specify foo or bar mode
+EOF
+}
+
+parse_args()
+{
+	case $1 in
+	a) ALTERNATIVE=1;;
+	f) MODE="$2";;
+	esac
+}
+
+do_test()
+{
+	tst_res TPASS "Test $1 passed with data '$2': a: '$ALTERNATIVE', f: '$MODE'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo0': a: '0', f: 'foo'
+# test 2 TPASS: Test 1 passed with data 'bar': a: '0', f: 'foo'
+# test 3 TPASS: Test 1 passed with data 'd dd': a: '0', f: 'foo'
+# 
+# Summary:
+# passed   3
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.sh b/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.sh
new file mode 100755
index 000000000..9a1bb9cf7
--- /dev/null
+++ b/lib/newlib_tests/shell/test.TST_TEST_DATA_IFS.sh
@@ -0,0 +1,27 @@ 
+#!/bin/sh
+#
+# Example test with tests in a single function, using $TST_TEST_DATA and
+# $TST_TEST_DATA_IFS
+#
+
+TST_TESTFUNC=do_test
+TST_TEST_DATA="foo:bar:d dd"
+TST_TEST_DATA_IFS=":"
+. tst_test.sh
+
+do_test()
+{
+	tst_res TPASS "Test $1 passed with data '$2'"
+}
+
+tst_run
+# output:
+# test 1 TPASS: Test 1 passed with data 'foo'
+# test 2 TPASS: Test 1 passed with data 'bar'
+# test 3 TPASS: Test 1 passed with data 'd dd'
+# 
+# Summary:
+# passed   3
+# failed   0
+# skipped  0
+# warnings 0
diff --git a/lib/newlib_tests/test_sh_newlib.sh b/lib/newlib_tests/test_sh_newlib.sh
new file mode 100755
index 000000000..bf335dd6e
--- /dev/null
+++ b/lib/newlib_tests/test_sh_newlib.sh
@@ -0,0 +1,105 @@ 
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+# © 2018 SUSE LLC.
+# 
+# Author: Christian Lanig <clanig@suse.com>
+
+setup()
+{
+	path_sav=$PATH
+	PATH=""$PATH":$(dirname $0)/../../testcases/lib/"
+	color_blue='\033[1;34m'
+	color_green='\033[1;32m'
+	color_red='\033[1;31m'
+	standard_color='\033[0m'
+	tmp_dir="/tmp/sh_lib_tst-$$/"
+	mkdir $tmp_dir || cleanup 1
+	sh_lib_tst_dir="$(dirname $0)/shell/"
+	tst_files="$(ls $sh_lib_tst_dir)"
+	return 0
+}
+
+cleanup()
+{
+	[ -d "$tmp_dir" ] && rm -rf "$tmp_dir"
+	PATH="$PATH_sav"
+	exit $1
+}
+
+print_help()
+{
+	printf "#############################################################\n"
+	printf "# This script iterates over test cases for the new shell    #\n"
+	printf "# library and verifies the output.                          #\n"
+	printf "#############################################################\n"
+	printf "\n"
+	printf "Usage:\n"
+	printf "\t$(basename $0)\n"
+	printf "\t$(basename $0) [test file 1] [test file 2] ...\n\n"
+	exit 0;
+}
+
+parse_params()
+{
+	[ -n "$1" ] && tst_files=
+	while [ -n "$1" ]; do
+		case "$1" in
+			--help) print_help;;
+			-h) print_help;;
+			-*)
+				printf "Unknown positional parameter "$1".\n"
+				cleanup 1;;
+			*) tst_files="$tst_files $1";;
+		esac
+		shift
+	done
+	return 0
+}
+
+verify_output()
+{
+	[ -e "$sh_lib_tst_dir$tst" ] || { printf "$tst not found\n" && \
+			cleanup 1 ;}
+	# read all lines after line: `# output:`, and strip `# ` from beginning
+	sed -n -e '/^# output:/,$ p;' "$sh_lib_tst_dir$tst" | sed '1d; s/^# //'\
+			> "$tmp_dir$tst.wanted" || cleanup 1
+
+	actual_output=$($sh_lib_tst_dir$tst)
+	# remove control signs, add newline at EOF and store in temporary file
+	actual_output=$(printf "$actual_output\n" > $tmp_dir$tst".actual") ||
+			cleanup 1
+
+	cmp $tmp_dir$tst".actual" $tmp_dir$tst".wanted" > /dev/null && return 0
+	return 1
+}
+
+run_tests()
+{
+	pass_cnt=0
+	fail_cnt=0
+	printf "\n"
+	for tst in $tst_files; do
+		if verify_output; then
+			pass_cnt=$(expr $pass_cnt + 1)
+			printf ""$color_green"TPASS$standard_color $tst"
+			printf "\n"
+		else
+			fail_cnt=$(expr $fail_cnt + 1)
+			printf ""$color_red"TFAIL$standard_color $tst\n"
+			printf ""$color_blue"Diff:$standard_color\n"
+			diff -u "$tmp_dir$tst.actual" "$tmp_dir$tst.wanted"
+			printf "\n"
+		fi
+	done
+	printf "\nSummary:\n"
+	printf ""$color_red"Failed:$standard_color $fail_cnt\n"
+	printf ""$color_green"Passed:$standard_color $pass_cnt\n\n"
+	return $fail_cnt
+}
+
+setup
+parse_params "$@"
+run_tests
+res=$?
+cleanup $res