PATCH: add dejagnu(1) multi-launcher

Jacob Bachmeyer <[email protected]>
Newsgroups gmane.comp.sysutils.dejagnu.general
Message-ID <[email protected]>
This patch is the long-promised dejagnu(1) multi-launcher, although only 
the launcher itself, and the first command to make it useful will be in 
a later patch.

I had previously mentioned some files that were part of this work that 
have been renamed between then and now.  Specifically, 
"testsuite/lib/dejagnu.exp" is now "testsuite/lib/launcher.exp" and the 
entire "testsuite/dejagnu.launcher/" directory has been renamed to 
"testsuite/launcher.all/" after I decided that, since the launcher is 
run using a LAUNCHER variable rather than a DEJAGNU variable (obvious 
potential for confusion with that bad choice that was avoided from the 
start), using "--tool launcher" to test the launcher is more appropriate 
than using "--tool dejagnu" even though the launcher is installed under 
the "dejagnu" name.

Shellcheck gives one minor complaint in dejagnu as written; I have 
chosen to ignore that complaint, since the $(grep ...) on line 130 is:  
(1) extracting the version number (which is unlikely to contain spaces) 
from runtest.exp and (2) substituted into an "echo" command, where 
word-splitting does not matter.  I have chosen to ignore this warning, 
following the advice of the GNU Coding Standards not to make the program 
ugly to placate a static analyzer.  Emacs highlights that code much 
better and more readably without the quotes anyway.

----
NEWS item:
?. A shell command "dejagnu" is added as a place to hang various auxiliary
   commands not directly involved with running tests.  The "runtest"
   command will remain for that purpose for the forseeable future.
----
ChangeLog entries:
	* Makefile.am (EXTRA_DIST): Add "dejagnu" launcher script.
	(bin_SCRIPTS): Likewise.
	(TESTSUITE_FILES): Add testsuite for same.
	(DEJATOOL): Add "launcher" to list of tools to test.
	(dist_man_MANS): Add man page for "dejagnu" launcher.

	* doc/dejagnu.texi (Running other DejaGnu commands): New chapter.
	(Invoking dejagnu): New node for dejagnu(1) launcher script.

	* doc/dejagnu.1: New man page.

	* dejagnu: New script.

	* testsuite/launcher.all/command.exp: New file.
	* testsuite/launcher.all/command/commands/bar-baz.awk: New file.
	* testsuite/launcher.all/command/commands/bar.awk: New file.
	* testsuite/launcher.all/command/commands/bar.sh: New file.
	* testsuite/launcher.all/command/commands/baz-quux.gawk: New file.
	* testsuite/launcher.all/command/commands/foo.sh: New file.
	* testsuite/launcher.all/command/commands/foo.tcl: New file.
	* testsuite/launcher.all/interp.exp: New file.
	* testsuite/launcher.all/verbose.exp: New file.
	* testsuite/lib/launcher.exp: New file.
----
patch:
----
diff --git a/dejagnu b/dejagnu
new file mode 100755
index 0000000..774ff81
--- /dev/null
+++ b/dejagnu
@@ -0,0 +1,454 @@
+#!/bin/sh
+#
+# Copyright (C) 2018 Free Software Foundation, Inc.
+#
+# This file is part of DejaGnu.
+#
+# DejaGnu is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# DejaGnu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DejaGnu; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Portions from runtest Copyright (C) 1992-2016 Free Software Foundation, Inc.
+
+# This script was written by Jacob Bachmeyer.  Portions of this script are
+# adapted from the existing runtest script originally written by Rob Savoye.
+
+# This script finds an implementation for the command given, finds the
+# proper interpreter, and then dispatches the command.  This script can
+# either be run with a command name as the first (few) argument(s), via a
+# link from the command name, or some combination of those.
+
+# shellcheck disable=SC2209
+# The shellcheck tool complains about assigning certain constant strings to
+# variables.  In this script, the intended meaning is obvious in context.
+
+# ##help
+# #Usage: dejagnu <command> [--help|options...]
+# #Usage: dejagnu --help
+# #Usage: dejagnu --version
+# #	--help			Print help text
+# #	--version		Print DejaGnu version
+# ##end
+
+# list of extensions supported for commands in priority order
+readonly Variants="gawk awk tcl exp bash sh"
+
+## Recognize options
+
+# For testing and development
+override_ext=
+if test x"$1" = x--DGTimpl ; then
+    override_ext=$2
+    shift 2
+fi
+
+want_help=false
+want_version=false
+verbose=0
+for a in "$@"; do
+    case $a in
+	--help)				want_help=true ;;
+	-v|--v|-verbose|--verbose)	verbose=$((verbose + 1)) ;;
+	-V|--V|-version|--version)	want_version=true ;;
+    esac
+done
+
+if expr "$verbose" \> 0 > /dev/null ; then
+    echo Verbose level is $verbose
+fi
+
+## Get the file name of this script and deduce @bindir@.
+
+bindir="$(echo "$0" | sed -e 's@/[^/]*$@@')"
+if expr "$verbose" \> 0 > /dev/null ; then
+    echo Running launcher from "$bindir"
+fi
+
+## Find the commands.
+
+# If running from source tree, they are in ./commands/ relative to this script.
+# If installed, they are in @datadir@/dejagnu/commands/ on the system.
+
+# This makes the same assumption as in runtest that one of these holds:
+#
+#   @datadir@ == @bindir@/../share
+#   @datadir@ == @bindir@/../../share
+#   @datadir@ == /usr/share
+#   @datadir@ == /usr/local/share
+
+if test -n "$DEJAGNULIBS" ; then
+    commdir="${DEJAGNULIBS}/commands"
+    datadir="${DEJAGNULIBS}"
+elif test -d "${bindir}/commands" && test -f "${bindir}/runtest.exp" ; then
+    if expr "$verbose" \> 1 > /dev/null ; then
+	echo Running from source directory
+    fi
+    commdir="${bindir}/commands"
+    datadir="${bindir}"
+else
+    commdir=
+    for i in \
+	"$(echo "$bindir" | sed -e 's@/[^/]*$@/share/dejagnu@')" \
+	"$(echo "$bindir" | sed -e 's@/[^/]*/[^/]*$@/share/dejagnu@')" \
+	/usr/share/dejagnu /usr/local/share/dejagnu
+    do
+	if expr "$verbose" \> 1 > /dev/null ; then
+	    echo Probing directory "$i"/commands
+	fi
+	if test -d "$i"/commands ; then
+	    commdir="$i"/commands
+	    datadir="$i"
+	    break
+	fi
+    done
+fi
+
+if test -z "${commdir}" ; then
+    echo ERROR: could not find command directory.
+    exit 2
+fi
+
+if expr "$verbose" \> 0 > /dev/null ; then
+    echo Looking for commands in "$commdir"
+fi
+
+## Get the name of the requested command.
+
+# Are we just looking for version information?
+if $want_version ; then
+    echo 'dejagnu auxiliary launcher (DejaGnu)' \
+	$(grep '^set frame_version' "${datadir}/runtest.exp" \
+	| sed 's/^[^0-9]*//')
+    exit 0
+fi
+
+# Remove any leading autoconf platform prefix and the "dejagnu" prefix.
+command="$(basename "$0" | sed -e 's/^.*-\?dejagnu-\?//')"
+commext=
+
+while true
+do
+    if test -z "${command}" ; then
+	if expr "$1" : - > /dev/null ; then
+	    break
+	fi
+	command="$1"
+	shift
+    fi
+    if expr "$verbose" \> 2 > /dev/null ; then
+	echo Looking for "${commdir}/${command}.*"
+    fi
+    for ext in ${Variants}
+    do
+	if test -f "${commdir}/${command}.$ext" ; then
+	    commext="$commext $ext"
+	fi
+    done
+    if expr "$1" : - > /dev/null ; then
+	break
+    fi
+    if test -n "$commext" ; then
+	break
+    fi
+    if test -n "$1" ; then
+	command="${command}-$1"
+	shift
+    else
+	break
+    fi
+done
+
+if test -z "$commext" && test -n "$command" ; then
+    echo ERROR: could not resolve command "$command"
+    exit 2
+fi
+
+if expr "$verbose" \> 0 > /dev/null ; then
+    if test -n "$command"; then
+	if expr "$verbose" \> 1 > /dev/null ; then
+	    echo Found subcommand "$command" with variants: "$commext"
+	else
+	    echo Found subcommand "$command"
+	fi
+    else
+	echo Running nothing.
+    fi
+fi
+
+## Find interpreters.
+
+# Awk and GNU awk
+if test -n "$AWK" ; then
+    awkbin="$AWK"
+elif test -x "${bindir}/awk" ; then
+    awkbin="${bindir}/awk"
+else
+    awkbin=awk
+fi
+if test -n "$GAWK" ; then
+    gawkbin="$GAWK"
+elif test -x "${bindir}/gawk" ; then
+    gawkbin="${bindir}/gawk"
+else
+    gawkbin=gawk
+fi
+if command -v "$awkbin" > /dev/null 2>&1 ; then
+    have_awk=true
+else
+    have_awk=false
+fi
+if command -v "$gawkbin" > /dev/null 2>&1 ; then
+    have_gawk=true
+else
+    have_gawk=false
+fi
+# substitute GNU awk for awk if needed
+if $have_gawk ; then
+    if $have_awk ; then : ; else
+	have_awk=$have_gawk
+	awkbin=$gawkbin
+    fi
+fi
+# is "awk" actually GNU Awk?
+if $have_awk ; then
+    if "$awkbin" --version | sed 1q | grep -qi 'GNU Awk' ; then
+	have_gawk_as_awk=true
+    else
+	have_gawk_as_awk=false
+    fi
+fi
+if expr "$verbose" \> 2 > /dev/null ; then
+    if $have_awk ; then
+	echo Awk interpreter is "$awkbin"
+    else
+	echo Awk interpreter was not found
+    fi
+    if $have_gawk ; then
+	echo GNU Awk interpreter is "$gawkbin"
+    else
+	echo GNU Awk interpreter was not found
+    fi
+fi
+
+
+# Bash
+if test -n "$BASH" ; then
+    bashbin="$BASH"
+elif test -x "${bindir}/bash" ; then
+    bashbin="${bindir}/bash"
+elif test -x /bin/bash ; then
+    bashbin=/bin/bash
+else
+    bashbin=bash
+fi
+if command -v "$bashbin" > /dev/null 2>&1 ; then
+    have_bash=true
+else
+    have_bash=false
+fi
+if expr "$verbose" \> 2 > /dev/null ; then
+    if $have_bash ; then
+	echo Bash interpreter is "$bashbin"
+    else
+	echo Bash interpreter was not found
+    fi
+fi
+
+# Bourne shell
+#  This script is running, therefore we have a Bourne shell.
+have_sh=true
+
+# Expect
+#  DejaGnu configure bails out if Expect is not available, but this script
+#  can be run from the source directory without first running configure.
+if test -n "$EXPECT" ; then
+    expectbin="$EXPECT"
+elif test -x "${bindir}/expect" ; then
+    expectbin="${bindir}/expect"
+else
+    expectbin=expect
+fi
+if command -v "$expectbin" > /dev/null 2>&1 ; then
+    have_expect=true
+else
+    have_expect=false
+fi
+if expr "$verbose" \> 2 > /dev/null ; then
+    if $have_expect ; then
+	echo Expect interpreter is "$expectbin"
+    else
+	echo Expect interpreter was not found
+    fi
+fi
+
+# Tcl
+if test -n "$TCLSH" ; then
+    tclbin="$TCLSH"
+elif test -x "${bindir}/tclsh" ; then
+    tclbin="${bindir}/tclsh"
+else
+    tclbin=tclsh
+fi
+# substitute expect if needed
+if command -v "$tclbin" > /dev/null 2>&1 ; then :
+elif command -v "$expectbin" > /dev/null 2>&1 ; then tclbin="$expectbin"
+fi
+if command -v "$tclbin" > /dev/null 2>&1 ; then
+    have_tcl=true
+else
+    have_tcl=false
+fi
+if expr "$verbose" \> 2 > /dev/null ; then
+    if $have_tcl ; then
+	echo Tcl interpreter is "$tclbin"
+    else
+	echo Tcl interpreter was not found
+    fi
+fi
+
+## Select a variant.
+
+if test -n "$override_ext" ; then
+    selected_ext="$override_ext"
+else
+    selected_ext=
+    for v in $commext
+    do
+	case $v in
+	    awk)
+		if $have_awk ; then
+		    selected_ext=awk
+		    break
+		fi
+		;;
+	    bash)
+		if $have_bash ; then
+		    selected_ext=bash
+		    break
+		fi
+		;;
+	    exp)
+		if $have_expect ; then
+		    selected_ext=exp
+		    break
+		fi
+		;;
+	    gawk)
+		if $have_gawk ; then
+		    selected_ext=gawk
+		    break
+		fi
+		;;
+	    tcl)
+		if $have_tcl ; then
+		    selected_ext=tcl
+		    break
+		fi
+		;;
+	    sh)
+		selected_ext=sh
+		break
+		;;
+	    *)
+		echo ERROR: '(select-variant)' unrecognized variant "$v"
+		;;
+	esac
+    done
+    if test -z "$selected_ext" && test -n "$command" ; then
+	echo ERROR: no variant of "$command" was selected
+	exit 2
+    fi
+fi
+
+if test -n "$command" && expr "$verbose" \> 0 > /dev/null ; then
+    if test -n "$override_ext" ; then
+	echo Selected variant "$selected_ext" by override
+    else
+	echo Selected variant "$selected_ext"
+    fi
+fi
+
+## Dispatch to the selected command.
+
+# Are we just looking for a usage message?
+if $want_help ; then
+    if test -z "$command" ; then
+	# want help on the launcher itself
+	help_file=$0
+    else
+	help_file="${commdir}/${command}.${selected_ext}"
+    fi
+    if test ! -r "$help_file" ; then
+	echo ERROR: file "'$help_file'" is not readable
+	exit 2
+    fi
+    if grep -q '#help' "$help_file" \
+	&& grep -q '#end' "$help_file"; then : ; else
+	echo ERROR: file "'$help_file'" does not contain a help message
+	exit 2
+    fi
+    help_prefix_pat=$(grep '#help' "$help_file" \
+	| sed 's/#help.*$//;1q' | tr '[:print:][:blank:]' .)
+    if expr "$verbose" \> 1 > /dev/null ; then
+	echo Extracting help from "'$help_file'" with prefix "'$help_prefix_pat'"
+    fi
+    sed -n < "$help_file" \
+	-e '1,/#help/d' \
+	-e '/^'"$help_prefix_pat"'#end/q' \
+	-e 's/^'"$help_prefix_pat"'//;p'
+    exit 0
+fi
+
+if test -z "$command" ; then
+    if test -n "$override_ext" ; then
+	case $selected_ext in
+	    awk)	$have_awk;	exit $? ;;
+	    bash)	$have_bash;	exit $? ;;
+	    exp)	$have_expect;	exit $? ;;
+	    gawk)	$have_gawk;	exit $? ;;
+	    tcl)	$have_tcl;	exit $? ;;
+	    sh)		$have_sh;	exit $? ;;
+	    *)				exit  2 ;;
+	esac
+    else
+	echo ERROR: no command given
+	exit 2
+    fi
+fi
+
+case $selected_ext in
+    awk)
+	if $have_gawk_as_awk ; then
+	    exec "$awkbin" --posix -f "${commdir}/${command}.awk" -- ${1+"$@"}
+	else
+	    exec "$awkbin" -f "${commdir}/${command}.awk" -- ${1+"$@"}
+	fi
+	;;
+    bash)   exec "$bashbin" -- "${commdir}/${command}.bash" ${1+"$@"} ;;
+    exp)    exec "$expectbin" -- "${commdir}/${command}.exp" ${1+"$@"} ;;
+    gawk)   exec "$gawkbin" -f "${commdir}/${command}.gawk" -- ${1+"$@"} ;;
+    tcl)    exec "$tclbin" "${commdir}/${command}.tcl" ${1+"$@"} ;;
+    sh)     exec "${commdir}/${command}.sh" ${1+"$@"} ;;
+    echo)
+	echo command: "${command}"
+	echo args: ${1+"$@"}
+	exit 0
+	;;
+    *)
+	echo ERROR: '(run-variant)' unrecognized variant "$selected_ext"
+	exit 2
+	;;
+esac
+
+
+#EOF
diff --git a/doc/dejagnu.1 b/doc/dejagnu.1
new file mode 100644
index 0000000..70facb0
--- /dev/null
+++ b/doc/dejagnu.1
@@ -0,0 +1,141 @@
+.\" Copyright (C) 2018  Free Software Foundation, Inc
+.\" You may distribute this file under the terms of the GNU Free
+.\" Documentation License.
+.Dd December 17, 2018
+.Os GNU
+.Dt DEJAGNU 1 URM
+.Sh NAME
+.Nm dejagnu
+.Nd DejaGnu auxiliary command launcher
+.Sh SYNOPSIS
+.Nm dejagnu
+.Ao Ar command Ac
+.Op Fl -help \*(Ba Ar options...
+.Nm
+.Fl -help
+.Nm
+.Fl -version
+.Sh DESCRIPTION
+The
+.Nm
+command finds a script that implements the requested
+.Ar command ,
+selects from multiple implementations if available
+according to a fixed internal list, and executes the command.
+.Sh OPTIONS
+.Bl -tag -width ".Fl -version"
+.It Fl -help
+Print a help message instead of running a command.
+If no command is given, prints brief usage for
+.Nm
+itself.
+.It Fl V , -version
+Print a version banner for the launcher itself including the version of DejaGnu.
+Any command given is ignored.
+.It Fl v , -verbose
+Emit additional output describing the operation of the
+.Nm
+launcher itself.
+This option is also passed on to the invoked command.
+.El
+.Pp
+All arguments after the command name are passed to the invoked command.
+.Sh ENVIRONMENT
+.Bl -tag -width ".Ev DEJAGNULIBS"
+.It Ev DEJAGNULIBS
+If set, the location of DejaGnu's library in the filesystem.
+The search described in
+.Sx FILES
+does not happen if
+.Ev DEJAGNULIBS
+is set.
+.It Ev AWK
+Full file name for an Awk interpreter that may or may not actually be GNU Awk.
+If not set,
+.Ev PATH
+will be searched for an
+.Nm awk
+program.
+If the Awk interpreter is actually GNU Awk, the
+.Fl -posix
+option will be given if an Awk implementation is used.
+.It Ev GAWK
+Full file name for GNU Awk.  If not set,
+.Ev PATH
+will be searched for a
+.Nm gawk
+program.
+.It Ev BASH
+Full file name for GNU Bash.  If not set,
+.Ev PATH
+will be searched for a
+.Nm bash
+program.
+Note that Bash itself sets this variable, even when run as
+.Nm sh ,
+even when running a script.
+.It Ev EXPECT
+Full file name for Expect, which is a Tcl interpreter with the Expect
+extension already loaded.  If not set,
+.Ev PATH
+will be searched for an
+.Nm expect
+program.  Note that the DejaGnu core is written in Expect, so this
+interpreter should always be available.
+.It Ev TCLSH
+Full file name for a Tcl interpreter.  If not set,
+.Ev PATH
+will be searched for a
+.Nm tclsh
+program.
+.El
+.Pp
+Note that GNU Awk is considered a superset of Awk and that Expect is
+considered a superset of Tcl, allowing the former to be used to run scripts
+written for the latter.
+This means that, while Awk programs will generally be run with GNU
+extensions disabled using the
+.Fl -posix
+option to GNU Awk, Tcl programs may be run with either
+.Nm tclsh
+or
+.Nm expect
+and should be written accordingly.
+.Sh FILES
+.Bl -tag -width ".Pa $DEJAGNULIBS/commands"
+.It Pa $DEJAGNULIBS/commands
+If
+.Ev DEJAGNULIBS
+is set, all command scripts are expected to be in this directory.
+.El
+Otherwise, the first directory that actually exists in the following list
+is used, where
+.Pa @bindir@
+represents the directory containing
+.Nm
+itself.
+.Bl -item -offset indent
+.It
+.Pa @bindir@/../share/dejagnu/commands
+.It
+.Pa @bindir@/../../share/dejagnu/commands
+.It
+.Pa /usr/share/dejagnu/commands
+.It
+.Pa /usr/local/share/dejagnu/commands
+.El
+.\" .Sh EXAMPLES
+.Sh "SEE ALSO"
+The full documentation for DejaGnu is maintained as a Texinfo manual.
+If the
+.Nm info
+program is properly installed at your site, the command
+.Li info dejagnu
+should give you access to the complete manual.
+.Sh AUTHORS
+.An "Jacob Bachmeyer"
+.Sh BUGS
+The command name must precede all other arguments due to limitations of the
+shell.
+.\"  LocalWords:  Dt URM Nm DejaGnu Ao DEJAGNULIBS DejaGnu's Sx awk posix tclsh
+.\"  LocalWords:  tcl superset bindir usr Texinfo
diff --git a/doc/dejagnu.texi b/doc/dejagnu.texi
index c4b4467..b7561b4 100644
--- a/doc/dejagnu.texi
+++ b/doc/dejagnu.texi
@@ -50,6 +50,7 @@ Free Documentation License''.
 @menu
 * Introduction::
 * Running tests::
+* Running other DejaGnu commands::
 * Customizing DejaGnu::
 * Extending DejaGnu::
 * Unit testing::
@@ -75,6 +76,10 @@ Running tests
 * Running runtest: Runtest.
 * Output files: Output Files.
 
+Running other DejaGnu commands
+
+* Invoking dejagnu::            Command line options for the launcher itself.
+
 Customizing DejaGnu
 
 * Global config file::
@@ -421,7 +426,7 @@ tools you want to test; then, in each test directory, run @emph{make
 check} to build auxiliary programs required by some of the tests, and
 run the test suites.
 
-@node Running tests, Customizing DejaGnu, Introduction, Top
+@node Running tests, Running other DejaGnu commands, Introduction, Top
 @chapter Running tests
 
 There are two ways to execute a testsuite. The most common way is when
@@ -1008,7 +1013,81 @@ make the actions for fail-safe patterns produce messages starting with
 
 @end itemize
 
-@node Customizing DejaGnu, Extending DejaGnu, Running tests, Top
+@node Running other DejaGnu commands, Customizing DejaGnu, Running tests, Top
+@chapter Running other DejaGnu commands
+
+DejaGnu now features auxiliary commands not directly related to
+running tests, but somehow related to the broader purpose of testing.
+
+These commands are run via the @command{dejagnu} multiplex launcher,
+which locates an appropriate script and the required interpreter and
+then runs the requested command.
+
+@menu
+* Invoking dejagnu::            Command line options for the launcher itself.
+@end menu
+
+@node Invoking dejagnu,  , Running other DejaGnu commands, Running other DejaGnu commands
+@section Invoking @command{dejagnu}
+@cindex dejagnu, invoking
+
+The @command{dejagnu} launcher is primarily designed to pass most
+options on to the scripts that it runs, but does process the
+@option{--help} and @option{--version} options entirely internally,
+while also recognizing the @option{--verbose} option.
+
+@example
+@command{dejagnu} <command> [options...]
+@command{dejagnu} --help
+@command{dejagnu} --version
+@end example
+
+Note that the command names may contain multiple words.  In this case,
+the command can be given as separate arguments to @command{dejagnu} or
+combined with dashes(@samp{-}); both forms are equivalent.
+
+All words of the command name must appear before any options.  The
+search for a command terminates when an option is found.
+
+Note that the first valid command found is used.  A longer command
+name can be shadowed by a shorter command name that happens to be a
+prefix of the longer name, if the command name is given as multiple
+arguments.  The equivalent form with the longer command name combined
+using dashes into a single argument will correctly refer to the
+otherwise shadowed command.
+
+The @command{dejagnu} launcher can also be run using symbolic links,
+provided that the shell places the name under which @command{dejagnu}
+was invoked in @code{$0}.  Any dash-separated words after ``dejagnu''
+in the name of such a link are taken to be the leading words of a
+command name.
+
+The @command{dejagnu} launcher supports alternate implementations
+depending upon available interpreters.
+
+Options for the @command{dejagnu} launcher itself cannot be
+abbreviated, since the launcher has no way to know which abbreviations
+are unique and which would be ambiguous to the invoked command.
+
+@table @asis
+
+@item @code{--help}
+Print a help message instead of running a command.
+
+@item @code{-V}, @code{--version}
+Print a version banner for the launcher itself including the
+version of DejaGnu.  Any command given is ignored.
+
+@item @code{-v}, @code{--verbose}
+Emit additional output describing the inner workings of the
+@command{dejagnu} launcher.  This option is also passed on to the
+invoked command.
+
+@end table
+
+All arguments after the command name are passed to the invoked command.
+
+@node Customizing DejaGnu, Extending DejaGnu, Running other DejaGnu commands, Top
 @chapter Customizing DejaGnu
 @cindex customization
 
diff --git a/testsuite/launcher.all/command.exp b/testsuite/launcher.all/command.exp
new file mode 100644
index 0000000..eb7b219
--- /dev/null
+++ b/testsuite/launcher.all/command.exp
@@ -0,0 +1,210 @@
+# Copyright (C) 2018 Free Software Foundation, Inc.
+#
+# This file is part of DejaGnu.
+#
+# DejaGnu is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# DejaGnu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DejaGnu; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+
+# This file was written by Jacob Bachmeyer.
+
+# each element:  { launcher_alias
+#			{ name args envars exit_code output_re... }... }
+set tests {
+    { dejagnu
+	{ "#" direct calls }
+
+	{ "dejagnu foo as Tcl"
+	    {foo -v -v} {EXPECT=true  TCLSH=true } 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant tcl" }
+	{ "dejagnu foo as Tcl via Expect"
+	    {foo -v -v} {EXPECT=true  TCLSH=bogus} 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant tcl" }
+	{ "dejagnu foo as shell"
+	    {foo -v -v} {EXPECT=bogus TCLSH=bogus} 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant sh" }
+
+	{ "dejagnu bar as Awk"
+	    {bar -v -v} {AWK=true  GAWK=true } 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant awk" }
+	{ "dejagnu bar as Awk via GNU Awk"
+	    {bar -v -v} {AWK=bogus GAWK=true } 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant awk" }
+	{ "dejagnu bar as shell"
+	    {bar -v -v} {AWK=bogus GAWK=bogus} 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant sh" }
+
+	{ "dejagnu bar shadows bar-baz"
+	    {bar baz -v -v} {AWK=true} 0
+	    "Found subcommand bar with variants:  awk sh" }
+	{ "dejagnu bar-baz works"
+	    {bar-baz -v -v} {AWK=true} 0
+	    "Found subcommand bar-baz with variants:  awk" }
+	{ "dejagnu bar-baz fails without Awk"
+	    {bar-baz -v -v} {AWK=bogus GAWK=bogus} 2
+	    "Found subcommand bar-baz with variants:  awk\n"
+	    "ERROR: no variant of bar-baz was selected" }
+
+	{ "dejagnu baz-quux works"
+	    {baz-quux -v -v} {GAWK=true} 0
+	    "Found subcommand baz-quux with variants:  gawk\n"
+	    "Selected variant gawk" }
+	{ "dejagnu baz quux works"
+	    {baz quux -v -v} {GAWK=true} 0
+	    "Found subcommand baz-quux with variants:  gawk\n"
+	    "Selected variant gawk" }
+
+	{ "#" invalid direct calls }
+
+	{ "dejagnu -v bar fails"	{-v bar} {} 2
+	    "Running nothing.\nERROR: no command given" }
+
+	{ "dejagnu \"\" bar fails"	{"" bar} {} 2 "ERROR: " }
+	{ "dejagnu \"\" \"\" bar fails"	{"" "" bar} {} 2 "ERROR: " }
+
+	{ "dejagnu baz \"\" quux fails"	{baz "" quux} {GAWK=true} 2 "ERROR: " }
+
+	{ "dejagnu bar \"\" baz finds bar subcommand"
+	    {--DGTimpl echo bar "" baz} {} 0
+	    "command: bar\nargs:  baz" }
+
+	{ "#" help system }
+
+	{ "dejagnu --help works"	{--help} {} 0 "Usage: dejagnu" }
+
+	{ "dejagnu foo --help works if shell variant selected"
+	    {foo --help} {EXPECT=bogus TCLSH=bogus} 0
+	    "foo.sh help message" }
+	{ "dejagnu foo --help fails if Tcl variant selected"
+	    {foo --help} {EXPECT=true  TCLSH=true } 2
+	    "does not contain a help message" }
+    }
+
+    { dejagnu-foo
+	{ "#" calls via dejagnu-foo link }
+	{ "dejagnu-foo as Tcl"
+	    {-v -v} {EXPECT=true  TCLSH=true } 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant tcl" }
+	{ "dejagnu-foo as Tcl via Expect"
+	    {-v -v} {EXPECT=true  TCLSH=bogus} 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant tcl" }
+	{ "dejagnu-foo as shell"
+	    {-v -v} {EXPECT=bogus TCLSH=bogus} 0
+	    "Found subcommand foo with variants:  tcl sh\n"
+	    "Selected variant sh" }
+    }
+
+    { dejagnu-bar
+	{ "#" calls via dejagnu-bar link }
+	{ "dejagnu-bar as Awk"
+	    {-v -v} {AWK=true  GAWK=true } 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant awk" }
+	{ "dejagnu-bar as Awk via GNU Awk"
+	    {-v -v} {AWK=bogus GAWK=true } 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant awk" }
+	{ "dejagnu-bar as shell"
+	    {-v -v} {AWK=bogus GAWK=bogus} 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant sh" }
+
+	{ "#" false hybrid:  bar shadows bar-baz }
+	{ "dejagnu-bar as Awk shadows bar-baz"
+	    {baz -v -v} {AWK=true} 0
+	    "Found subcommand bar with variants:  awk sh\n"
+	    "Selected variant awk" }
+    }
+
+    { dejagnu-bar-baz
+	{ "#" calls via dejagnu-bar-baz link }
+	{ "dejagnu-bar-baz as Awk"
+	    {-v -v} {AWK=true  GAWK=true } 0
+	    "Found subcommand bar-baz with variants:  awk\n"
+	    "Selected variant awk" }
+	{ "dejagnu-bar-baz as Awk via GNU Awk"
+	    {-v -v} {AWK=bogus GAWK=true } 0
+	    "Found subcommand bar-baz with variants:  awk\n"
+	    "Selected variant awk" }
+	{ "dejagnu-bar-baz fails without Awk"
+	    {-v -v} {AWK=bogus GAWK=bogus} 2
+	    "Found subcommand bar-baz with variants:  awk\n"
+	    "ERROR: no variant of bar-baz was selected" }
+    }
+
+    { dejagnu-baz
+	{ "#" hybrid:  link as prefix }
+	{ "dejagnu-baz quux as GNU Awk"
+	    {quux -v -v} {GAWK=true} 0
+	    "Found subcommand baz-quux with variants:  gawk\n"
+	    "Selected variant gawk" }
+	{ "dejagnu-baz quux fails without GNU Awk"
+	    {quux -v -v} {GAWK=bogus AWK=true} 2
+	    "Found subcommand baz-quux with variants:  gawk\n"
+	    "ERROR: no variant of baz-quux was selected" }
+    }
+
+}
+
+# Build the test environment.
+
+# This must be done at run-time in the build directory because symbolic
+# links are not allowed in GNU source tarballs.
+
+catch {
+    file link -symbolic \
+	[testsuite file -object -test command share dejagnu commands] \
+	[testsuite file -source -test command commands]
+}
+foreach alias_item $tests {
+    catch {
+	file link -symbolic \
+	    [testsuite file -object -test command bin [lindex $alias_item 0]] \
+	    $LAUNCHER
+    }
+}
+if { ![file exists \
+	   [testsuite file -object -test command share dejagnu commands]] } {
+    foreach alias_set $tests {
+	skip_dejagnu_launcher_tests \
+	    "Could not set up test environment." \
+	    unsupported [lrange $alias_set 1 end]
+    }
+    return
+}
+
+# Run the tests.
+
+if { [which true] == 0 || [which bogus] != 0 } {
+    foreach alias_set $tests {
+	skip_dejagnu_launcher_tests \
+	    "Either 'true' is not on PATH or 'bogus' is." \
+	    unresolved [lrange $alias_set 1 end]
+    }
+} else {
+    foreach alias_set $tests {
+	run_dejagnu_launcher_tests \
+	    [testsuite file -object -test command bin [lindex $alias_set 0]] \
+	    [lrange $alias_set 1 end]
+    }
+}
+
+#EOF
diff --git a/testsuite/launcher.all/command/commands/bar-baz.awk b/testsuite/launcher.all/command/commands/bar-baz.awk
new file mode 100644
index 0000000..28753c8
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/bar-baz.awk
@@ -0,0 +1 @@
+# empty "hidden" command
diff --git a/testsuite/launcher.all/command/commands/bar.awk b/testsuite/launcher.all/command/commands/bar.awk
new file mode 100644
index 0000000..1bb8bf6
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/bar.awk
@@ -0,0 +1 @@
+# empty
diff --git a/testsuite/launcher.all/command/commands/bar.sh b/testsuite/launcher.all/command/commands/bar.sh
new file mode 100755
index 0000000..c52d3c2
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/bar.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exit 0
diff --git a/testsuite/launcher.all/command/commands/baz-quux.gawk b/testsuite/launcher.all/command/commands/baz-quux.gawk
new file mode 100644
index 0000000..9c4ff45
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/baz-quux.gawk
@@ -0,0 +1 @@
+# empty -*- Awk -*- file
diff --git a/testsuite/launcher.all/command/commands/foo.sh b/testsuite/launcher.all/command/commands/foo.sh
new file mode 100755
index 0000000..154687f
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/foo.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# ##help
+# #foo.sh help message
+# ##end
+
+exit 0
diff --git a/testsuite/launcher.all/command/commands/foo.tcl b/testsuite/launcher.all/command/commands/foo.tcl
new file mode 100644
index 0000000..1bb8bf6
--- /dev/null
+++ b/testsuite/launcher.all/command/commands/foo.tcl
@@ -0,0 +1 @@
+# empty
diff --git a/testsuite/launcher.all/interp.exp b/testsuite/launcher.all/interp.exp
new file mode 100644
index 0000000..02e763e
--- /dev/null
+++ b/testsuite/launcher.all/interp.exp
@@ -0,0 +1,57 @@
+# Copyright (C) 2018 Free Software Foundation, Inc.
+#
+# This file is part of DejaGnu.
+#
+# DejaGnu is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# DejaGnu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DejaGnu; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+
+# This file was written by Jacob Bachmeyer.
+
+# each element: { name args envars exit_code output_re... }
+set tests {
+    { "have shell, always" {--DGTimpl sh} {} 0 "" }
+    { "report error probing 'echo'" {--DGTimpl echo} {} 2 "" }
+
+    { "have no Awk"	{--DGTimpl awk}  {AWK=bogus GAWK=bogus} 1 "" }
+    { "have Awk"	{--DGTimpl awk}  {AWK=true  GAWK=bogus} 0 "" }
+
+    { "have no GNU Awk"	{--DGTimpl gawk} {GAWK=bogus} 1 "" }
+    { "have GNU Awk"	{--DGTimpl gawk} {GAWK=true }  0 "" }
+
+    { "GNU Awk substitutes for Awk"
+			{--DGTimpl awk}  {AWK=bogus GAWK=true} 0 "" }
+
+    { "have no Tcl"	{--DGTimpl tcl}  {TCLSH=bogus EXPECT=bogus} 1 "" }
+    { "have Tcl"	{--DGTimpl tcl}  {TCLSH=true  EXPECT=bogus} 0 "" }
+
+    { "have no Expect"	{--DGTimpl exp}  {EXPECT=bogus} 1 "" }
+    { "have Expect"	{--DGTimpl exp}  {EXPECT=true }  0 "" }
+
+    { "Expect substitutes for Tcl"
+			{--DGTimpl tcl}  {TCLSH=bogus EXPECT=true} 0 "" }
+}
+
+if { [which true] == 0 || [which bogus] != 0 } {
+    skip_dejagnu_launcher_tests \
+	"Either 'true' is not on PATH or 'bogus' is." \
+	unresolved $tests
+} elseif { ![file isdirectory [file join [file dirname $LAUNCHER] commands]] } {
+    skip_dejagnu_launcher_tests \
+	"The 'commands' directory is not present in the source tree." \
+	unsupported $tests
+} else {
+    run_dejagnu_launcher_tests $LAUNCHER $tests
+}
+
+#EOF
diff --git a/testsuite/launcher.all/verbose.exp b/testsuite/launcher.all/verbose.exp
new file mode 100644
index 0000000..a45ca2d
--- /dev/null
+++ b/testsuite/launcher.all/verbose.exp
@@ -0,0 +1,55 @@
+# Copyright (C) 2018 Free Software Foundation, Inc.
+#
+# This file is part of DejaGnu.
+#
+# DejaGnu is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# DejaGnu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DejaGnu; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+
+# This file was written by Jacob Bachmeyer.
+
+# each element: { name args envars exit_code output_re... }
+set tests {
+    { "empty command" {} {} 2 "ERROR: no command given" }
+    { "empty; verbose 1" {-v} {} 2
+	"Verbose level is 1\nRunning launcher [^\n]*\n"
+	"Looking for commands [^\n]*\nRunning nothing.\n"
+	"ERROR: no command given" }
+    { "empty; verbose 2" {-v -v} {} 2
+	"Verbose level is 2\nRunning launcher [^\n]*\n"
+	"Running from source directory\n"
+	"Looking for commands [^\n]*\nRunning nothing.\n"
+	"ERROR: no command given" }
+    { "empty; verbose 3" {-v -v -v} {} 2
+	"Verbose level is 3\nRunning launcher [^\n]*\n"
+	"Running from source directory\n"
+	"Looking for commands [^\n]*\nRunning nothing.\n"
+	"Awk interpreter [^\n]*\nGNU Awk interpreter [^\n]*\n"
+	"Bash interpreter [^\n]*\n"
+	"Expect interpreter [^\n]*\nTcl interpreter [^\n]*\n"
+	"ERROR: no command given" }
+}
+
+lappend tests [list "check version" {--version} {} 0 \
+		   [format "dejagnu auxiliary launcher \\(DejaGnu\\) %s" \
+			$frame_version]]
+
+if { ![file isdirectory [file join [file dirname $LAUNCHER] commands]] } {
+    skip_dejagnu_launcher_tests \
+	"The 'commands' directory is not present in the source tree." \
+	unsupported $tests
+} else {
+    run_dejagnu_launcher_tests $LAUNCHER $tests
+}
+
+#EOF
diff --git a/testsuite/lib/launcher.exp b/testsuite/lib/launcher.exp
new file mode 100644
index 0000000..906f722
--- /dev/null
+++ b/testsuite/lib/launcher.exp
@@ -0,0 +1,110 @@
+# Copyright (C) 2018 Free Software Foundation, Inc.
+#
+# This file is part of DejaGnu.
+#
+# DejaGnu is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# DejaGnu is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with DejaGnu; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+
+# This file was written by Jacob Bachmeyer.
+
+if { ![info exists LAUNCHER] } {
+    set LAUNCHER \
+	[file join [file dirname [testsuite file -source -top]] dejagnu]
+}
+verbose "Using LAUNCHER $LAUNCHER" 2
+
+if { [which $LAUNCHER] == 0 } {
+    perror "Can't find LAUNCHER = $LAUNCHER"
+    exit 2
+}
+
+# run dejagnu(1) LAUNCHER with ARGLIST, returning { output exit_code }
+proc dejagnu_run { launcher arglist envlist } {
+    global errorCode
+
+    set exec_cmd [list exec]
+    if { [llength $envlist] > 0 } {
+	lappend exec_cmd env
+	foreach var $envlist { lappend exec_cmd $var }
+    }
+    lappend exec_cmd $launcher
+
+    # reset errorCode
+    catch { error }
+
+    catch { eval $exec_cmd $arglist } output
+
+    if { [lindex $errorCode 0] eq "CHILDSTATUS" } {
+	return [list $output [lindex $errorCode 2]]
+    } else {
+	return [list $output 0]
+    }
+}
+
+# evaluate a test against LAUNCHER, returning true if it passes
+# TEST is a list:  { name arglist envlist exit_code output_re... }
+proc try_dejagnu_launcher { launcher test } {
+    foreach part [lrange $test 4 end] { append re $part }
+
+    if { [llength [lindex $test 2]] > 0 } {
+	verbose "Spawning \"env [lindex $test 2] $launcher [lindex $test 1]\" ..."
+    } else {
+	verbose "Spawning \"$launcher [lindex $test 1]\" ..."
+    }
+    verbose "Expecting to match {$re} ..." 2
+    set result [dejagnu_run $launcher [lindex $test 1] [lindex $test 2]]
+    verbose "Exit code [lindex $result 1]; output {[lindex $result 0]}" 2
+
+    if { [regexp $re [lindex $result 0]]
+	 && [lindex $test 3] == [lindex $result 1] } {
+	return 1
+    } else {
+	return 0
+    }
+}
+
+proc run_dejagnu_launcher_tests { launcher tests } {
+    foreach test $tests {
+	if { [lindex $test 0] == "#" } {
+	    # ignore comments in test list
+	} elseif { [llength $test] == 1 } {
+	    # name only is a stub
+	    untested [lindex $test 0]
+	} elseif { [try_dejagnu_launcher $launcher $test] } {
+	    pass [lindex $test 0]
+	} else {
+	    fail [lindex $test 0]
+	}
+    }
+}
+
+proc skip_dejagnu_launcher_tests { why result tests } {
+    perror $why 0
+    foreach test $tests {
+	if { [lindex $test 0] == "#" } {
+	    # ignore comments in test list
+	} else {
+	    $result [lindex $test 0]
+	}
+    }
+}
+
+# stub: dejagnu(1) itself is non-interactive
+proc dejagnu_exit {} {}
+
+# stub: dejagnu(1) does not have a separate version number
+proc dejagnu_version {} {
+}
+
+#EOF
----


-- Jacob
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.