automake
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [GSoC] "proof of concept": use automake TAP driver with the Git test


From: Stefano Lattarini
Subject: Re: [GSoC] "proof of concept": use automake TAP driver with the Git testsuite.
Date: Mon, 22 Aug 2011 19:07:41 +0200
User-agent: KMail/1.13.3 (Linux/2.6.30-2-686; KDE/4.4.4; i686; ; )

On Sunday 14 August 2011, Stefano Lattarini wrote:
> It's nice to see that the new TAP support in Automake, albeit still
> incomplete, can work correctly with the testsuite of an important,
> real-world package like Git.  The attached patch, to be applied to
> the maint branch of git (at the moment of writing, that is commit
> `v1.7.6-43-g0906f6e'), should demonstrate this.
>
And here is an extension to the previous proof-of-concept that allows
the use of the new "awk + shell" implementation of the TAP driver.
See the attached patch (for the latest git maint branch, i.e., commit
`v1.7.6-178-gec09954').

How to reproduce:

 $ cd /tmp
 $ [save the attached patch in /tmp]
 $ git clone git://git.sv.gnu.org/automake.git
 $ cd automake
 $ git checkout origin/test-protocols # Should be commit `v1.11-1054-g30913e0'
 $ ./bootstrap && ./configure --prefix=`pwd`/_inst && make install
 $ export PATH=`pwd`/_inst/bin:$PATH
 $ cd ..
 $ git clone git://git.kernel.org/pub/scm/git/git.git
 $ cd git
 $ git checkout origin/maint # Should be commit `v1.7.6-178-gec09954'
 $ git am -3 /tmp/0001-allow-automake-TAP-driver-to-run-the-git-testsuite.patch
 $ make autotoolize
 $ ./configure && make
 $ cd t
 $ make check # Run the git testsuite with its home-brewed test runner.
 $ make check t-harness=am-perl # Use the older perl-prototyped TAP driver.
 $ make check t-harness=am-awk  # Use the newer "awk + shell" TAP driver.

I've not tested it very thouroughly, but it seems to work nicely.

Regards,
  Stefano

From 5c980aaaf3501b28c02139627396d458f6f50313 Mon Sep 17 00:00:00 2001
Message-Id: <address@hidden>
From: Stefano Lattarini <address@hidden>
Date: Sun, 14 Aug 2011 11:06:05 +0200
Subject: [PATCH] allow automake TAP driver to run the git testsuite

---
 .gitignore                 |    3 +
 Makefile                   |   40 +++-
 aclocal.m4 => acinclude.m4 |    0
 configure.ac               |   14 +-
 t/.gitignore               |    4 +
 t/Makefile                 |    6 +
 t/Makefile.auto.am         |  607 +++++++++++++++++++++++++++++++++++++++++++
 t/tap-driver.pl            |  451 ++++++++++++++++++++++++++++++++
 t/tap-driver.sh            |  608 ++++++++++++++++++++++++++++++++++++++++++++
 t/test-lib.sh              |    6 +-
 10 files changed, 1725 insertions(+), 14 deletions(-)
 rename aclocal.m4 => acinclude.m4 (100%)
 create mode 100644 t/Makefile.auto.am
 create mode 100755 t/tap-driver.pl
 create mode 100755 t/tap-driver.sh

diff --git a/.gitignore b/.gitignore
index 8572c8c..5e25114 100644
--- a/.gitignore
+++ b/.gitignore
@@ -210,6 +210,9 @@
 /config.mak.autogen
 /config.mak.append
 /configure
+/aclocal.m4
+/install-sh
+/missing
 /tags
 /TAGS
 /cscope*
diff --git a/Makefile b/Makefile
index 75b407c..a361b9e 100644
--- a/Makefile
+++ b/Makefile
@@ -251,6 +251,11 @@ all::
 # dependency rules.
 #
 # Define NATIVE_CRLF if your platform uses CRLF for line endings.
+#
+
+ACLOCAL = aclocal
+AUTOCONF = autoconf
+AUTOMAKE = automake
 
 GIT-VERSION-FILE: FORCE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1818,12 +1823,23 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)): % : 
unimplemented.sh
        mv address@hidden $@
 endif # NO_PYTHON
 
-configure: configure.ac
-       $(QUIET_GEN)$(RM) $@ $<+ && \
-       sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           $< > $<+ && \
-       autoconf -o $@ $<+ && \
-       $(RM) $<+
+configure_deps = acinclude.m4 aclocal.m4 configure.ac GIT-VERSION-FILE
+
+aclocal.m4: configure.ac GIT-VERSION-FILE
+       $(ACLOCAL)
+configure: $(configure_deps)
+       $(AUTOCONF)
+config.status: configure
+       ./config.status --recheck
+t/Makefile.auto: config.status t/Makefile.auto.in
+       ./config.status t/Makefile.auto
+t/Makefile.auto.in: t/Makefile.auto.am $(configure_deps)
+       $(AUTOMAKE) --add-missing --copy t/Makefile.auto
+
+autotoolize: aclocal.m4 configure t/Makefile.auto.in
+# For compatibility with automake-generated remake rules.
+am--refresh: autotoolize t/Makefile.auto
+.PHONY: autotoolize am--refresh
 
 # These can record GIT_VERSION
 git.o git.spec \
@@ -2353,8 +2369,14 @@ dist-doc:
 
 ### Cleaning rules
 
-distclean: clean
-       $(RM) configure
+autoclean:
+       $(RM) configure aclocal.m4 config.status config.cache \
+             install-sh missing t/Makefile.auto t/Makefile.auto.in \
+             config.log config.mak.autogen config.mak.append
+       $(RM) -r autom4te.cache
+.PHONY: autoclean
+
+distclean: clean autoclean
        $(RM) po/git.pot
 
 clean:
@@ -2365,8 +2387,6 @@ clean:
        $(RM) -r bin-wrappers
        $(RM) -r $(dep_dirs)
        $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h $(ETAGS_TARGET) 
tags cscope*
-       $(RM) -r autom4te.cache
-       $(RM) config.log config.mak.autogen config.mak.append config.status 
config.cache
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
diff --git a/aclocal.m4 b/acinclude.m4
similarity index 100%
rename from aclocal.m4
rename to acinclude.m4
diff --git a/configure.ac b/configure.ac
index 048a1d4..410ea64 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,8 +1,10 @@
 #                                               -*- Autoconf -*-
 # Process this file with autoconf to produce a configure script.
 
-AC_PREREQ(2.59)
-AC_INIT([git], [@@GIT_VERSION@@], address@hidden)
+AC_PREREQ([2.62])
+AC_INIT([git],
+        m4_esyscmd([cat GIT-VERSION-FILE | sed 's/.*= *//' | tr -d '\n']),
+        address@hidden)
 
 AC_CONFIG_SRCDIR([git.c])
 
@@ -12,6 +14,9 @@ config_in=config.mak.in
 
 echo "# ${config_append}.  Generated by configure." > "${config_append}"
 
+AC_CONFIG_AUX_DIR([.])
+AM_INIT_AUTOMAKE([-Wall -Werror])
+AC_SUBST([am_protect], [""])
 
 ## Definitions of macros
 # GIT_CONF_APPEND_LINE(LINE)
@@ -999,7 +1004,10 @@ AC_SUBST(PTHREAD_LIBS)
 AC_SUBST(NO_PTHREADS)
 
 ## Output files
-AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"])
+AC_CONFIG_FILES([
+  "${config_file}":"${config_in}":"${config_append}"
+  t/Makefile.auto
+])
 AC_OUTPUT
 
 
diff --git a/t/.gitignore b/t/.gitignore
index 4e731dc..cd6f5a7 100644
--- a/t/.gitignore
+++ b/t/.gitignore
@@ -1,3 +1,7 @@
 /trash directory*
 /test-results
 /.prove
+/Makefile.auto.in
+/Makefile.auto
+/*.trs
+/*.log
diff --git a/t/Makefile b/t/Makefile
index 9046ec9..9a680a8 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -6,6 +6,10 @@
 -include ../config.mak.autogen
 -include ../config.mak
 
+ifneq ($(t-harness),)
+include Makefile.auto
+else
+
 #GIT_TEST_OPTS=--verbose --debug
 SHELL_PATH ?= $(SHELL)
 PERL_PATH ?= /usr/bin/perl
@@ -112,3 +116,5 @@ smoke_report: smoke
        | grep -v ^Redirecting
 
 .PHONY: pre-clean $(T) aggregate-results clean valgrind smoke smoke_report
+
+endif
diff --git a/t/Makefile.auto.am b/t/Makefile.auto.am
new file mode 100644
index 0000000..b32bc92
--- /dev/null
+++ b/t/Makefile.auto.am
@@ -0,0 +1,607 @@
+## -*- Makefile -*-
+
+AUTOMAKE_OPTIONS = color-tests parallel-tests no-dist \
+                   -Wno-portability -Wno-override
+
+TEST_EXTENSIONS = .sh
+SH_LOG_DRIVER = $(log-driver-$(t-harness))
+
+define log-driver-am-awk
+env AM_TAP_AWK='$(AWK)' $(SHELL) ./tap-driver.sh
+endef
+
+define log-driver-am-perl
+$(PERL_PATH) ./tap-driver.pl
+endef
+
address@hidden@ifeq ($(SH_LOG_DRIVER),)
address@hidden@$(error invalid test harness implementation $(t-harness))
address@hidden@endif
+
+## For compatibility with TAP::Harness.  The test scripts use this
+## variable to determine whether they are running directory or
+## through an harness, and to adapt their behaviour accordingly.
+AM_TESTS_ENVIRONMENT = HARNESS_ACTIVE=yes
+
+TESTS = $(all_TESTS) # Can be overridden at make time.
+
+## Nullify some targets that we don't need.
+install:
+uninstall:
+install-strip:
+install-exec:
+install-data:
+## For consistency with the non-automake Makefile.
+all: check check-list-of-tests
+.PHONY: install uninstall all
+
+## Temporary files used in the `check-list-of-tests' target below.
+am__tmk = tests-in-makefile-list.tmp
+am__tfs = tests-on-filesystem-list.tmp
+am__tdf = diff-in-tests-lists.tmp
+
+## Check that the list of tests given in the Makefile is equal to the
+## list of all test scripts in the Automake testsuite.
+.PHONY: check-list-of-tests
+check-list-of-tests:
+## Prefer unified diffs over plain diffs, for readability.
+        @if diff -u /dev/null /dev/null >/dev/null 2>&1; then \
+          diff='diff -u'; \
+        else \
+          diff='diff'; \
+        fi; \
+        LC_ALL=C; export LC_ALL; \
+## List of tests in Makefile.
+        for t in $(all_TESTS); do \
+          echo "$$t"; \
+        done | sort >$(am__tmk); \
+## List of tests on filesystem.
+        for t in t[0-9][0-9][0-9][0-9]-*.sh; do \
+          echo "$$t"; \
+        done | sort >$(am__tfs); \
+## Compare them.
+        if $$diff $(am__tmk) $(am__tfs) >$(am__tdf); then \
+           result=0; \
+        else \
+           echo 'List of tests in Makefile and on filesystem differ' >&2; \
+           echo "+ $$diff in-makefile on-filesystem" >&2; \
+           cat $(am__tdf) >&2; \
+           result=1; \
+        fi; \
+        rm -f $(am__tmk) $(am__tfs) $(am__tdf); \
+        exit $$result;
+
+clean-local:
+       rm -f $(am__tmk) $(am__tfs) $(am__tdf)
+
+all_TESTS = \
+  t0000-basic.sh \
+  t0001-init.sh \
+  t0002-gitfile.sh \
+  t0003-attributes.sh \
+  t0004-unwritable.sh \
+  t0005-signals.sh \
+  t0006-date.sh \
+  t0010-racy-git.sh \
+  t0020-crlf.sh \
+  t0021-conversion.sh \
+  t0022-crlf-rename.sh \
+  t0023-crlf-am.sh \
+  t0024-crlf-archive.sh \
+  t0025-crlf-auto.sh \
+  t0026-eol-config.sh \
+  t0030-stripspace.sh \
+  t0040-parse-options.sh \
+  t0050-filesystem.sh \
+  t0055-beyond-symlinks.sh \
+  t0060-path-utils.sh \
+  t0061-run-command.sh \
+  t0070-fundamental.sh \
+  t0080-vcs-svn.sh \
+  t0081-line-buffer.sh \
+  t0100-previous.sh \
+  t0101-at-syntax.sh \
+  t0201-gettext-fallbacks.sh \
+  t1000-read-tree-m-3way.sh \
+  t1001-read-tree-m-2way.sh \
+  t1002-read-tree-m-u-2way.sh \
+  t1003-read-tree-prefix.sh \
+  t1004-read-tree-m-u-wf.sh \
+  t1005-read-tree-reset.sh \
+  t1006-cat-file.sh \
+  t1007-hash-object.sh \
+  t1008-read-tree-overlay.sh \
+  t1009-read-tree-new-index.sh \
+  t1010-mktree.sh \
+  t1011-read-tree-sparse-checkout.sh \
+  t1012-read-tree-df.sh \
+  t1020-subdirectory.sh \
+  t1021-rerere-in-workdir.sh \
+  t1050-large.sh \
+  t1100-commit-tree-options.sh \
+  t1200-tutorial.sh \
+  t1300-repo-config.sh \
+  t1301-shared-repo.sh \
+  t1302-repo-version.sh \
+  t1303-wacky-config.sh \
+  t1304-default-acl.sh \
+  t1400-update-ref.sh \
+  t1401-symbolic-ref.sh \
+  t1402-check-ref-format.sh \
+  t1410-reflog.sh \
+  t1411-reflog-show.sh \
+  t1412-reflog-loop.sh \
+  t1420-lost-found.sh \
+  t1450-fsck.sh \
+  t1500-rev-parse.sh \
+  t1501-worktree.sh \
+  t1502-rev-parse-parseopt.sh \
+  t1503-rev-parse-verify.sh \
+  t1504-ceiling-dirs.sh \
+  t1505-rev-parse-last.sh \
+  t1506-rev-parse-diagnosis.sh \
+  t1507-rev-parse-upstream.sh \
+  t1508-at-combinations.sh \
+  t1509-root-worktree.sh \
+  t1510-repo-setup.sh \
+  t1511-rev-parse-caret.sh \
+  t2000-checkout-cache-clash.sh \
+  t2001-checkout-cache-clash.sh \
+  t2002-checkout-cache-u.sh \
+  t2003-checkout-cache-mkdir.sh \
+  t2004-checkout-cache-temp.sh \
+  t2005-checkout-index-symlinks.sh \
+  t2006-checkout-index-basic.sh \
+  t2007-checkout-symlink.sh \
+  t2008-checkout-subdir.sh \
+  t2009-checkout-statinfo.sh \
+  t2010-checkout-ambiguous.sh \
+  t2011-checkout-invalid-head.sh \
+  t2012-checkout-last.sh \
+  t2013-checkout-submodule.sh \
+  t2014-switch.sh \
+  t2015-checkout-unborn.sh \
+  t2016-checkout-patch.sh \
+  t2017-checkout-orphan.sh \
+  t2018-checkout-branch.sh \
+  t2019-checkout-ambiguous-ref.sh \
+  t2020-checkout-detach.sh \
+  t2021-checkout-overwrite.sh \
+  t2030-unresolve-info.sh \
+  t2050-git-dir-relative.sh \
+  t2100-update-cache-badpath.sh \
+  t2101-update-index-reupdate.sh \
+  t2102-update-index-symlinks.sh \
+  t2103-update-index-ignore-missing.sh \
+  t2104-update-index-skip-worktree.sh \
+  t2105-update-index-gitfile.sh \
+  t2106-update-index-assume-unchanged.sh \
+  t2107-update-index-basic.sh \
+  t2200-add-update.sh \
+  t2201-add-update-typechange.sh \
+  t2202-add-addremove.sh \
+  t2203-add-intent.sh \
+  t2204-add-ignored.sh \
+  t2300-cd-to-toplevel.sh \
+  t3000-ls-files-others.sh \
+  t3001-ls-files-others-exclude.sh \
+  t3002-ls-files-dashpath.sh \
+  t3003-ls-files-exclude.sh \
+  t3004-ls-files-basic.sh \
+  t3010-ls-files-killed-modified.sh \
+  t3020-ls-files-error-unmatch.sh \
+  t3030-merge-recursive.sh \
+  t3031-merge-criscross.sh \
+  t3032-merge-recursive-options.sh \
+  t3040-subprojects-basic.sh \
+  t3050-subprojects-fetch.sh \
+  t3060-ls-files-with-tree.sh \
+  t3100-ls-tree-restrict.sh \
+  t3101-ls-tree-dirname.sh \
+  t3102-ls-tree-wildcards.sh \
+  t3103-ls-tree-misc.sh \
+  t3200-branch.sh \
+  t3201-branch-contains.sh \
+  t3202-show-branch-octopus.sh \
+  t3203-branch-output.sh \
+  t3210-pack-refs.sh \
+  t3300-funny-names.sh \
+  t3301-notes.sh \
+  t3302-notes-index-expensive.sh \
+  t3303-notes-subtrees.sh \
+  t3304-notes-mixed.sh \
+  t3305-notes-fanout.sh \
+  t3306-notes-prune.sh \
+  t3307-notes-man.sh \
+  t3308-notes-merge.sh \
+  t3309-notes-merge-auto-resolve.sh \
+  t3310-notes-merge-manual-resolve.sh \
+  t3311-notes-merge-fanout.sh \
+  t3400-rebase.sh \
+  t3401-rebase-partial.sh \
+  t3402-rebase-merge.sh \
+  t3403-rebase-skip.sh \
+  t3404-rebase-interactive.sh \
+  t3405-rebase-malformed.sh \
+  t3406-rebase-message.sh \
+  t3407-rebase-abort.sh \
+  t3408-rebase-multi-line.sh \
+  t3409-rebase-preserve-merges.sh \
+  t3410-rebase-preserve-dropped-merges.sh \
+  t3411-rebase-preserve-around-merges.sh \
+  t3412-rebase-root.sh \
+  t3413-rebase-hook.sh \
+  t3414-rebase-preserve-onto.sh \
+  t3415-rebase-autosquash.sh \
+  t3416-rebase-onto-threedots.sh \
+  t3417-rebase-whitespace-fix.sh \
+  t3418-rebase-continue.sh \
+  t3419-rebase-patch-id.sh \
+  t3500-cherry.sh \
+  t3501-revert-cherry-pick.sh \
+  t3502-cherry-pick-merge.sh \
+  t3503-cherry-pick-root.sh \
+  t3504-cherry-pick-rerere.sh \
+  t3505-cherry-pick-empty.sh \
+  t3506-cherry-pick-ff.sh \
+  t3507-cherry-pick-conflict.sh \
+  t3508-cherry-pick-many-commits.sh \
+  t3509-cherry-pick-merge-df.sh \
+  t3600-rm.sh \
+  t3700-add.sh \
+  t3701-add-interactive.sh \
+  t3702-add-edit.sh \
+  t3703-add-magic-pathspec.sh \
+  t3800-mktag.sh \
+  t3900-i18n-commit.sh \
+  t3901-i18n-patch.sh \
+  t3902-quoted.sh \
+  t3903-stash.sh \
+  t3904-stash-patch.sh \
+  t4000-diff-format.sh \
+  t4001-diff-rename.sh \
+  t4002-diff-basic.sh \
+  t4003-diff-rename-1.sh \
+  t4004-diff-rename-symlink.sh \
+  t4005-diff-rename-2.sh \
+  t4006-diff-mode.sh \
+  t4007-rename-3.sh \
+  t4008-diff-break-rewrite.sh \
+  t4009-diff-rename-4.sh \
+  t4010-diff-pathspec.sh \
+  t4011-diff-symlink.sh \
+  t4012-diff-binary.sh \
+  t4013-diff-various.sh \
+  t4014-format-patch.sh \
+  t4015-diff-whitespace.sh \
+  t4016-diff-quote.sh \
+  t4017-diff-retval.sh \
+  t4018-diff-funcname.sh \
+  t4019-diff-wserror.sh \
+  t4020-diff-external.sh \
+  t4021-format-patch-numbered.sh \
+  t4022-diff-rewrite.sh \
+  t4023-diff-rename-typechange.sh \
+  t4024-diff-optimize-common.sh \
+  t4025-hunk-header.sh \
+  t4026-color.sh \
+  t4027-diff-submodule.sh \
+  t4028-format-patch-mime-headers.sh \
+  t4029-diff-trailing-space.sh \
+  t4030-diff-textconv.sh \
+  t4031-diff-rewrite-binary.sh \
+  t4032-diff-inter-hunk-context.sh \
+  t4033-diff-patience.sh \
+  t4034-diff-words.sh \
+  t4035-diff-quiet.sh \
+  t4036-format-patch-signer-mime.sh \
+  t4037-diff-r-t-dirs.sh \
+  t4038-diff-combined.sh \
+  t4039-diff-assume-unchanged.sh \
+  t4040-whitespace-status.sh \
+  t4041-diff-submodule-option.sh \
+  t4042-diff-textconv-caching.sh \
+  t4043-diff-rename-binary.sh \
+  t4044-diff-index-unique-abbrev.sh \
+  t4045-diff-relative.sh \
+  t4046-diff-unmerged.sh \
+  t4047-diff-dirstat.sh \
+  t4048-diff-combined-binary.sh \
+  t4100-apply-stat.sh \
+  t4101-apply-nonl.sh \
+  t4102-apply-rename.sh \
+  t4103-apply-binary.sh \
+  t4104-apply-boundary.sh \
+  t4105-apply-fuzz.sh \
+  t4106-apply-stdin.sh \
+  t4107-apply-ignore-whitespace.sh \
+  t4109-apply-multifrag.sh \
+  t4110-apply-scan.sh \
+  t4111-apply-subdir.sh \
+  t4112-apply-renames.sh \
+  t4113-apply-ending.sh \
+  t4114-apply-typechange.sh \
+  t4115-apply-symlink.sh \
+  t4116-apply-reverse.sh \
+  t4117-apply-reject.sh \
+  t4118-apply-empty-context.sh \
+  t4119-apply-config.sh \
+  t4120-apply-popt.sh \
+  t4121-apply-diffs.sh \
+  t4122-apply-symlink-inside.sh \
+  t4123-apply-shrink.sh \
+  t4124-apply-ws-rule.sh \
+  t4125-apply-ws-fuzz.sh \
+  t4126-apply-empty.sh \
+  t4127-apply-same-fn.sh \
+  t4128-apply-root.sh \
+  t4129-apply-samemode.sh \
+  t4130-apply-criss-cross-rename.sh \
+  t4131-apply-fake-ancestor.sh \
+  t4132-apply-removal.sh \
+  t4133-apply-filenames.sh \
+  t4134-apply-submodule.sh \
+  t4135-apply-weird-filenames.sh \
+  t4150-am.sh \
+  t4151-am-abort.sh \
+  t4152-am-subjects.sh \
+  t4200-rerere.sh \
+  t4201-shortlog.sh \
+  t4202-log.sh \
+  t4203-mailmap.sh \
+  t4204-patch-id.sh \
+  t4205-log-pretty-formats.sh \
+  t4206-log-follow-harder-copies.sh \
+  t4207-log-decoration-colors.sh \
+  t4208-log-magic-pathspec.sh \
+  t4252-am-options.sh \
+  t4253-am-keep-cr-dos.sh \
+  t4300-merge-tree.sh \
+  t5000-tar-tree.sh \
+  t5001-archive-attr.sh \
+  t5100-mailinfo.sh \
+  t5150-request-pull.sh \
+  t5300-pack-object.sh \
+  t5301-sliding-window.sh \
+  t5302-pack-index.sh \
+  t5303-pack-corruption-resilience.sh \
+  t5304-prune.sh \
+  t5305-include-tag.sh \
+  t5306-pack-nobase.sh \
+  t5307-pack-missing-commit.sh \
+  t5400-send-pack.sh \
+  t5401-update-hooks.sh \
+  t5402-post-merge-hook.sh \
+  t5403-post-checkout-hook.sh \
+  t5404-tracking-branches.sh \
+  t5405-send-pack-rewind.sh \
+  t5406-remote-rejects.sh \
+  t5407-post-rewrite-hook.sh \
+  t5500-fetch-pack.sh \
+  t5501-fetch-push-alternates.sh \
+  t5502-quickfetch.sh \
+  t5503-tagfollow.sh \
+  t5505-remote.sh \
+  t5506-remote-groups.sh \
+  t5510-fetch.sh \
+  t5511-refspec.sh \
+  t5512-ls-remote.sh \
+  t5513-fetch-track.sh \
+  t5514-fetch-multiple.sh \
+  t5515-fetch-merge-logic.sh \
+  t5516-fetch-push.sh \
+  t5517-push-mirror.sh \
+  t5518-fetch-exit-status.sh \
+  t5519-push-alternates.sh \
+  t5520-pull.sh \
+  t5521-pull-options.sh \
+  t5522-pull-symlink.sh \
+  t5523-push-upstream.sh \
+  t5524-pull-msg.sh \
+  t5525-fetch-tagopt.sh \
+  t5526-fetch-submodules.sh \
+  t5530-upload-pack-error.sh \
+  t5531-deep-submodule-push.sh \
+  t5532-fetch-proxy.sh \
+  t5540-http-push.sh \
+  t5541-http-push.sh \
+  t5550-http-fetch.sh \
+  t5551-http-fetch.sh \
+  t5560-http-backend-noserver.sh \
+  t5561-http-backend.sh \
+  t5600-clone-fail-cleanup.sh \
+  t5601-clone.sh \
+  t5602-clone-remote-exec.sh \
+  t5700-clone-reference.sh \
+  t5701-clone-local.sh \
+  t5702-clone-options.sh \
+  t5704-bundle.sh \
+  t5705-clone-2gb.sh \
+  t5706-clone-branch.sh \
+  t5710-info-alternate.sh \
+  t5800-remote-helpers.sh \
+  t6000-rev-list-misc.sh \
+  t6001-rev-list-graft.sh \
+  t6002-rev-list-bisect.sh \
+  t6003-rev-list-topo-order.sh \
+  t6004-rev-list-path-optim.sh \
+  t6005-rev-list-count.sh \
+  t6006-rev-list-format.sh \
+  t6007-rev-list-cherry-pick-file.sh \
+  t6008-rev-list-submodule.sh \
+  t6009-rev-list-parent.sh \
+  t6010-merge-base.sh \
+  t6011-rev-list-with-bad-commit.sh \
+  t6012-rev-list-simplify.sh \
+  t6013-rev-list-reverse-parents.sh \
+  t6014-rev-list-all.sh \
+  t6015-rev-list-show-all-parents.sh \
+  t6016-rev-list-graph-simplify-history.sh \
+  t6017-rev-list-stdin.sh \
+  t6018-rev-list-glob.sh \
+  t6019-rev-list-ancestry-path.sh \
+  t6020-merge-df.sh \
+  t6021-merge-criss-cross.sh \
+  t6022-merge-rename.sh \
+  t6023-merge-file.sh \
+  t6024-recursive-merge.sh \
+  t6025-merge-symlinks.sh \
+  t6026-merge-attr.sh \
+  t6027-merge-binary.sh \
+  t6028-merge-up-to-date.sh \
+  t6029-merge-subtree.sh \
+  t6030-bisect-porcelain.sh \
+  t6031-merge-recursive.sh \
+  t6032-merge-large-rename.sh \
+  t6033-merge-crlf.sh \
+  t6034-merge-rename-nocruft.sh \
+  t6035-merge-dir-to-symlink.sh \
+  t6036-recursive-corner-cases.sh \
+  t6037-merge-ours-theirs.sh \
+  t6038-merge-text-auto.sh \
+  t6040-tracking-info.sh \
+  t6050-replace.sh \
+  t6060-merge-index.sh \
+  t6101-rev-parse-parents.sh \
+  t6110-rev-list-sparse.sh \
+  t6120-describe.sh \
+  t6200-fmt-merge-msg.sh \
+  t6300-for-each-ref.sh \
+  t6500-gc.sh \
+  t7001-mv.sh \
+  t7003-filter-branch.sh \
+  t7004-tag.sh \
+  t7005-editor.sh \
+  t7006-pager.sh \
+  t7007-show.sh \
+  t7008-grep-binary.sh \
+  t7010-setup.sh \
+  t7011-skip-worktree-reading.sh \
+  t7012-skip-worktree-writing.sh \
+  t7060-wtstatus.sh \
+  t7101-reset.sh \
+  t7102-reset.sh \
+  t7103-reset-bare.sh \
+  t7104-reset.sh \
+  t7105-reset-patch.sh \
+  t7110-reset-merge.sh \
+  t7111-reset-table.sh \
+  t7201-co.sh \
+  t7300-clean.sh \
+  t7400-submodule-basic.sh \
+  t7401-submodule-summary.sh \
+  t7402-submodule-rebase.sh \
+  t7403-submodule-sync.sh \
+  t7405-submodule-merge.sh \
+  t7406-submodule-update.sh \
+  t7407-submodule-foreach.sh \
+  t7408-submodule-reference.sh \
+  t7500-commit.sh \
+  t7501-commit.sh \
+  t7502-commit.sh \
+  t7503-pre-commit-hook.sh \
+  t7504-commit-msg-hook.sh \
+  t7505-prepare-commit-msg-hook.sh \
+  t7506-status-submodule.sh \
+  t7507-commit-verbose.sh \
+  t7508-status.sh \
+  t7509-commit.sh \
+  t7600-merge.sh \
+  t7601-merge-pull-config.sh \
+  t7602-merge-octopus-many.sh \
+  t7603-merge-reduce-heads.sh \
+  t7604-merge-custom-message.sh \
+  t7605-merge-resolve.sh \
+  t7606-merge-custom.sh \
+  t7607-merge-overwrite.sh \
+  t7608-merge-messages.sh \
+  t7609-merge-co-error-msgs.sh \
+  t7610-mergetool.sh \
+  t7611-merge-abort.sh \
+  t7700-repack.sh \
+  t7701-repack-unpack-unreachable.sh \
+  t7800-difftool.sh \
+  t7810-grep.sh \
+  t7811-grep-open.sh \
+  t8001-annotate.sh \
+  t8002-blame.sh \
+  t8003-blame-corner-cases.sh \
+  t8004-blame-with-conflicts.sh \
+  t8005-blame-i18n.sh \
+  t8006-blame-textconv.sh \
+  t8007-cat-file-textconv.sh \
+  t8008-blame-formats.sh \
+  t9001-send-email.sh \
+  t9010-svn-fe.sh \
+  t9100-git-svn-basic.sh \
+  t9101-git-svn-props.sh \
+  t9102-git-svn-deep-rmdir.sh \
+  t9103-git-svn-tracked-directory-removed.sh \
+  t9104-git-svn-follow-parent.sh \
+  t9105-git-svn-commit-diff.sh \
+  t9106-git-svn-commit-diff-clobber.sh \
+  t9107-git-svn-migrate.sh \
+  t9108-git-svn-glob.sh \
+  t9109-git-svn-multi-glob.sh \
+  t9110-git-svn-use-svm-props.sh \
+  t9111-git-svn-use-svnsync-props.sh \
+  t9112-git-svn-md5less-file.sh \
+  t9113-git-svn-dcommit-new-file.sh \
+  t9114-git-svn-dcommit-merge.sh \
+  t9115-git-svn-dcommit-funky-renames.sh \
+  t9116-git-svn-log.sh \
+  t9117-git-svn-init-clone.sh \
+  t9118-git-svn-funky-branch-names.sh \
+  t9119-git-svn-info.sh \
+  t9120-git-svn-clone-with-percent-escapes.sh \
+  t9121-git-svn-fetch-renamed-dir.sh \
+  t9122-git-svn-author.sh \
+  t9123-git-svn-rebuild-with-rewriteroot.sh \
+  t9124-git-svn-dcommit-auto-props.sh \
+  t9125-git-svn-multi-glob-branch-names.sh \
+  t9126-git-svn-follow-deleted-readded-directory.sh \
+  t9127-git-svn-partial-rebuild.sh \
+  t9128-git-svn-cmd-branch.sh \
+  t9129-git-svn-i18n-commitencoding.sh \
+  t9130-git-svn-authors-file.sh \
+  t9131-git-svn-empty-symlink.sh \
+  t9132-git-svn-broken-symlink.sh \
+  t9133-git-svn-nested-git-repo.sh \
+  t9134-git-svn-ignore-paths.sh \
+  t9135-git-svn-moved-branch-empty-file.sh \
+  t9136-git-svn-recreated-branch-empty-file.sh \
+  t9137-git-svn-dcommit-clobber-series.sh \
+  t9138-git-svn-authors-prog.sh \
+  t9139-git-svn-non-utf8-commitencoding.sh \
+  t9140-git-svn-reset.sh \
+  t9141-git-svn-multiple-branches.sh \
+  t9142-git-svn-shallow-clone.sh \
+  t9143-git-svn-gc.sh \
+  t9144-git-svn-old-rev_map.sh \
+  t9145-git-svn-master-branch.sh \
+  t9146-git-svn-empty-dirs.sh \
+  t9150-svk-mergetickets.sh \
+  t9151-svn-mergeinfo.sh \
+  t9152-svn-empty-dirs-after-gc.sh \
+  t9153-git-svn-rewrite-uuid.sh \
+  t9154-git-svn-fancy-glob.sh \
+  t9155-git-svn-fetch-deleted-tag.sh \
+  t9156-git-svn-fetch-deleted-tag-2.sh \
+  t9157-git-svn-fetch-merge.sh \
+  t9158-git-svn-mergeinfo.sh \
+  t9159-git-svn-no-parent-mergeinfo.sh \
+  t9200-git-cvsexportcommit.sh \
+  t9300-fast-import.sh \
+  t9301-fast-import-notes.sh \
+  t9350-fast-export.sh \
+  t9400-git-cvsserver-server.sh \
+  t9401-git-cvsserver-crlf.sh \
+  t9500-gitweb-standalone-no-errors.sh \
+  t9501-gitweb-standalone-http-status.sh \
+  t9502-gitweb-standalone-parse-output.sh \
+  t9600-cvsimport.sh \
+  t9601-cvsimport-vendor-branch.sh \
+  t9602-cvsimport-branches-tags.sh \
+  t9603-cvsimport-patchsets.sh \
+  t9700-perl-git.sh \
+  t9800-git-p4.sh
diff --git a/t/tap-driver.pl b/t/tap-driver.pl
new file mode 100755
index 0000000..2393346
--- /dev/null
+++ b/t/tap-driver.pl
@@ -0,0 +1,451 @@
+#! /usr/bin/env perl
+# Temporary/experimental TAP test driver for Automake.
+# TODO: should be rewritten portably (e.g., in awk or shell).
+
+# ---------------------------------- #
+#  Imports, static data, and setup.  #
+# ---------------------------------- #
+
+use warnings FATAL => 'all';
+use strict;
+use Getopt::Long ();
+use TAP::Parser;
+
+my $ME = "tap-driver";
+
+my $USAGE = <<'END';
+Usage:
+  tap-driver --test-name=NAME --log-file=PATH --trs-file=PATH
+             [--expect-failure={yes|no}] [--color-tests={yes|no}]
+             [--enable-hard-errors={yes|no}] [--ignore-exit]
+             [--diagnostic-string=STRING] [--merge|--no-merge]
+             [--comments|--no-comments] [--] TEST-COMMAND
+The `--test-name' and `--log-file' options are mandatory.
+END
+
+my $HELP = "$ME: TAP-aware test driver for Automake testsuite harness." .
+           "\n" . $USAGE;
+
+my $VERSION = '(experimental version)';
+
+# Keep this in sync with `lib/am/check.am:$(am__tty_colors)'.
+my %COLOR = (
+  red => "\e[0;31m",
+  grn => "\e[0;32m",
+  lgn => "\e[1;32m",
+  blu => "\e[1;34m",
+  mgn => "\e[0;35m",
+  brg => "\e[1m",
+  std => "\e[m",
+);
+
+# ------------------- #
+#  Global variables.  #
+# ------------------- #
+
+my $testno = 0;     # Number of test results seen so far.
+my $plan_seen = 0;  # Whether the TAP plan has been seen or not.
+my $parser;         # TAP parser object (will be initialized later).
+
+# When true, it means that the rest of the input stream cannot
+# contain any further TAP results.
+my $tap_stopped = 0;
+
+# ----------------- #
+#  Option parsing.  #
+# ----------------- #
+
+my %cfg = (
+  "color-tests" => 0,
+  "expect-failure" => 0,
+  "enable-hard-errors" => 1,
+  "merge" => 0,
+  "comments" => 0,
+  "ignore-exit" => 0,
+);
+
+my $test_script_name = undef;
+my $log_file = undef;
+my $trs_file = undef;
+my $diag_string = "#";
+
+Getopt::Long::GetOptions (
+    'help' => sub { print $HELP; exit 0; },
+    'version' => sub { print "$ME $VERSION\n"; exit 0; },
+    'test-name=s' => \$test_script_name,
+    'log-file=s' => \$log_file,
+    'trs-file=s' => \$trs_file,
+    'color-tests=s'  => \&bool_opt,
+    'expect-failure=s'  => \&bool_opt,
+    'enable-hard-errors=s' => \&bool_opt,
+    'diagnostic-string=s' => \$diag_string,
+    'comments' => sub { $cfg{"comments"} = 1; },
+    'no-comments' => sub { $cfg{"comments"} = 0; },
+    'merge' => sub { $cfg{"merge"} = 1; },
+    'no-merge' => sub { $cfg{"merge"} = 0; },
+    'ignore-exit' => sub { $cfg{"ignore-exit"} = 1; },
+  ) or exit 1;
+
+# ------------- #
+#  Prototypes.  #
+# ------------- #
+
+sub add_test_result ($);
+sub bool_opt ($$);
+sub colored ($$);
+sub copy_in_global_log ();
+sub decorate_result ($);
+sub extract_tap_comment ($);
+sub finish ();
+sub get_global_test_result ();
+sub get_test_exit_message ();
+sub get_test_results ();
+sub handle_tap_bailout ($);
+sub handle_tap_plan ($);
+sub handle_tap_test ($);
+sub main (@);
+sub must_recheck ();
+sub report ($;$);
+sub start (@);
+sub stringify_test_result ($);
+sub testsuite_error ($);
+sub write_test_results ();
+sub yn ($);
+
+# -------------- #
+#  Subroutines.  #
+# -------------- #
+
+sub bool_opt ($$)
+{
+  my ($opt, $val) = @_;
+  if ($val =~ /^(?:y|yes)\z/i)
+    {
+      $cfg{$opt} = 1;
+    }
+  elsif ($val =~ /^(?:n|no)\z/i)
+    {
+      $cfg{$opt} = 0;
+    }
+  else
+    {
+      die "invalid argument '$val' for option '$opt'\n";
+    }
+}
+
+# Convert a boolean to a "yes"/"no" string.
+sub yn ($)
+{
+  my $bool = shift;
+  return $bool ? "yes" : "no";
+}
+
+TEST_RESULTS :
+{
+  my (@test_results, %test_results);
+
+  sub add_test_result ($)
+  {
+    my $res = shift;
+    push @test_results, $res;
+    $test_results{$res} = 1;
+  }
+
+  sub get_test_results ()
+  {
+    return @test_results;
+  }
+
+  # Whether the test script should be re-run by "make recheck".
+  sub must_recheck ()
+  {
+    return grep { !/^(?:XFAIL|PASS|SKIP)$/ } (keys %test_results);
+  }
+
+  # Whether the content of the log file associated to this test should
+  # be copied into the "global" test-suite.log.
+  sub copy_in_global_log ()
+  {
+    return grep { not $_ eq "PASS" } (keys %test_results);
+  }
+
+  # FIXME: this can certainly be improved ...
+  sub get_global_test_result ()
+  {
+    my @results = keys %test_results;
+    return "ERROR" if exists $test_results{"ERROR"};
+    return "SKIP" if @results == 1 && $results[0] eq "SKIP";
+    return "FAIL" if exists $test_results{"FAIL"};
+    return "FAIL" if exists $test_results{"XPASS"};
+    return "PASS";
+  }
+
+}
+
+sub write_test_results ()
+{
+  open RES, ">", $trs_file or die "opening $trs_file: $!\n";
+  print RES ":global-test-result: " . get_global_test_result . "\n";
+  print RES ":recheck: " . yn (must_recheck) . "\n";
+  print RES ":copy-in-global-log: " . yn (copy_in_global_log) . "\n";
+  foreach my $result (get_test_results)
+    {
+      print RES ":test-result: $result\n";
+    }
+  close RES or die "closing $trs_file: $!\n";
+}
+
+sub start (@)
+{
+  # Redirect stderr and stdout to a temporary log file.  Save the
+  # original stdout stream, since we need it to print testsuite
+  # progress output.
+  open LOG, ">", $log_file or die "opening $log_file: $!\n";
+  open OLDOUT, ">&STDOUT" or die "duplicating stdout: $!\n";
+  open STDOUT, ">&LOG" or die "redirecting stdout: $!\n";
+  open STDERR, ">&LOG" or die "redirecting stderr: $!\n";
+  $parser = TAP::Parser->new ({ exec => address@hidden, merge => $cfg{merge} 
});
+  $parser->ignore_exit(1) if $cfg{"ignore-exit"};
+}
+
+sub get_test_exit_message ()
+{
+  # Flush all the remaining TAP stream, so that we can obtain the
+  # exit status of the TAP producer.
+  do {} while defined $parser->next;
+  my $wstatus = $parser->wait;
+  # Return an undefined value if the producer exited with success.
+  return unless $wstatus;
+  # Otherwise, determine whether it exited with error or was terminated
+  # by a signal.
+  use POSIX qw (WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
+  if (WIFEXITED ($wstatus))
+       {
+      return sprintf "exited with status %d", WEXITSTATUS ($wstatus);
+       }
+  elsif (WIFSIGNALED ($wstatus))
+       {
+      return sprintf "terminated by signal %d", WTERMSIG ($wstatus);
+       }
+  else
+       {
+         return "terminated abnormally";
+       }
+}
+
+sub finish ()
+{
+  if (!$cfg{"ignore-exit"} and my $msg = get_test_exit_message)
+    {
+      testsuite_error $msg;
+    }
+  write_test_results;
+  close LOG or die "closing $log_file: $!\n";
+  exit 0;
+}
+
+sub stringify_test_result ($)
+{
+  my $result = shift;
+  my $PASS = $cfg{"expect-failure"} ? "XPASS": "PASS";
+  my $FAIL = $cfg{"expect-failure"} ? "XFAIL": "FAIL";
+  if ($result->is_unplanned || $result->number != $testno || $tap_stopped)
+    {
+      return "ERROR";
+    }
+  elsif (!$result->directive)
+    {
+      return $result->is_ok ? $PASS: $FAIL;
+    }
+  elsif ($result->has_todo)
+    {
+      return $result->is_actual_ok ? "XPASS" : "XFAIL";
+    }
+  elsif ($result->has_skip)
+    {
+      return $result->is_ok ? "SKIP" : $FAIL;
+    }
+  die "INTERNAL ERROR"; # NOTREACHED
+}
+
+sub colored ($$)
+{
+  my ($color_name, $text) = @_;
+  return  $COLOR{$color_name} . $text . $COLOR{'std'};
+}
+
+sub decorate_result ($)
+{
+  my $result = shift;
+  return $result unless $cfg{"color-tests"};
+  my %color_for_result =
+    (
+      "ERROR" => 'mgn',
+      "PASS"  => 'grn',
+      "XPASS" => 'red',
+      "FAIL"  => 'red',
+      "XFAIL" => 'lgn',
+      "SKIP"  => 'blu',
+    );
+  if (my $color = $color_for_result{$result})
+    {
+      return colored ($color, $result);
+    }
+  else
+    {
+      return $result; # Don't colorize unknown stuff.
+    }
+}
+
+sub report ($;$)
+{
+  my ($msg, $result, $explanation) = (undef, @_);
+  if ($result =~ /^(?:X?(?:PASS|FAIL)|SKIP|ERROR)/)
+    {
+      $msg = ": $test_script_name";
+      add_test_result $result;
+    }
+  elsif ($result eq "#")
+    {
+      $msg = " $test_script_name:";
+    }
+  else
+    {
+      die "INTERNAL ERROR"; # NOTREACHED
+    }
+  $msg .= " $explanation" if defined $explanation;
+  $msg .= "\n";
+  # Output on console might be colorized.
+  print OLDOUT decorate_result ($result) . $msg;
+  # Log the result in the log file too, to help debugging (this is
+  # especially true when said result is a TAP error or "Bail out!").
+  print $result . $msg;
+}
+
+sub testsuite_error ($)
+{
+  report "ERROR", "- $_[0]";
+}
+
+sub handle_tap_test ($)
+{
+  $testno++;
+  my $test = shift;
+
+  my $test_result = stringify_test_result $test;
+  my $string = $test->number;
+  
+  if (my $description = $test->description)
+    {
+      $string .= " $description";
+    }
+
+  if ($tap_stopped)
+    {
+      $string .= " # AFTER LATE PLAN";
+    }
+  elsif ($test->is_unplanned)
+    {
+      $string .= " # UNPLANNED";
+    }
+  elsif ($test->number != $testno)
+    {
+      $string .= " # OUT-OF-ORDER (expecting $testno)";
+    }
+  elsif (my $directive = $test->directive)
+    {
+      $string .= " # $directive";
+      if (my $explanation = $test->explanation)
+        {
+          $string .= " $explanation";
+        }
+    }
+
+  report $test_result, $string;
+}
+
+sub handle_tap_plan ($)
+{
+  my $plan = shift;
+  # Only one plan per stream is acceptable.
+  testsuite_error "multiple test plans" if $plan_seen;
+  $plan_seen = 1;
+  # TAP plan must come either before or after *all* the TAP results.
+  # So, if we find it after having already seen at least one TAP result,
+  # set a flag signaling that no more TAP results are acceptable.
+  $tap_stopped = 1 if $testno >= 1;
+  # Nothing more to do, unless the plan contains a SKIP directive.
+  return
+    if not defined $plan->directive && length ($plan->directive) > 0;
+  my $explanation = $plan->explanation ?
+                    "- " . $plan->explanation : undef;
+  report "SKIP", $explanation;
+  finish;
+}
+
+sub handle_tap_bailout ($)
+{
+  my ($bailout, $msg) = ($_[0], "Bail out!");
+  $msg .= " " . $bailout->explanation if $bailout->explanation;
+  testsuite_error $msg;
+  finish;
+}
+
+sub extract_tap_comment ($)
+{
+  local $_ = shift;
+  if (/^\Q$diag_string\E(.*)$/o)
+    {
+      (my $comment = $1) =~ s/(?:^\s*|\s*$)//g;
+      return $comment;
+    }
+  return "";
+}
+
+sub main (@)
+{
+  start @_;
+
+  while (defined (my $cur = $parser->next))
+    {
+      # Verbatim copy any input line into the log file.
+      print $cur->raw . "\n";
+      if ($cur->is_plan)
+        {
+          handle_tap_plan ($cur);
+        }
+      elsif ($cur->is_test)
+        {
+          handle_tap_test ($cur);
+        }
+      elsif ($cur->is_bailout)
+        {
+          handle_tap_bailout ($cur);
+        }
+      elsif ($cfg{comments})
+        {
+          my $comment = extract_tap_comment ($cur->raw);
+          report "#", "$comment" if length $comment;
+       }
+    }
+  if (!$plan_seen)
+    {
+      testsuite_error "missing test plan";
+    }
+  elsif ($parser->tests_planned != $parser->tests_run)
+    {
+      my ($planned, $run) = ($parser->tests_planned, $parser->tests_run);
+      my $bad_amount = $run > $planned ? "many" : "few";
+      testsuite_error (sprintf "too %s tests run (expected %d, got %d)",
+                               $bad_amount, $planned, $run);
+    }
+  finish;
+}
+
+# ----------- #
+#  Main code. #
+# ----------- #
+
+main @ARGV;
+
+# vim: ft=perl ts=4 sw=4 et
diff --git a/t/tap-driver.sh b/t/tap-driver.sh
new file mode 100755
index 0000000..1255662
--- /dev/null
+++ b/t/tap-driver.sh
@@ -0,0 +1,608 @@
+#! /bin/sh
+# Copyright (C) 2011 Free Software Foundation, Inc.
+#
+# This program 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 2, or (at your option)
+# any later version.
+#
+# This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <address@hidden> or send patches to
+# <address@hidden>.
+
+scriptversion=2011-08-21.21; # UTC
+
+# Make unconditional expansion of undefined variables an error.  This
+# helps a lot in preventing typo-related bugs.
+set -u
+
+me=tap-driver.sh
+
+fatal ()
+{
+  echo "$me: fatal: $*" >&2
+  exit 1
+}
+
+usage_error ()
+{
+  echo "$me: $*" >&2
+  print_usage >&2
+  exit 2
+}
+
+print_usage ()
+{
+  cat <<END
+Usage:
+  tap-driver.sh --test-name=NAME --log-file=PATH --trs-file=PATH
+                [--expect-failure={yes|no}] [--color-tests={yes|no}]
+                [--enable-hard-errors={yes|no}] [--ignore-exit]
+                [--diagnostic-string=STRING] [--merge|--no-merge]
+                [--comments|--no-comments] [--] TEST-COMMAND
+The \`--test-name', \`--log-file' and \`--trs-file' options are mandatory.
+END
+}
+
+# TODO: better error handling in option parsing (in particular, ensure
+# TODO: $log_file, $trs_file and $test_name are defined).
+test_name= # Used for reporting.
+log_file=  # Where to save the result and output of the test script.
+trs_file=  # Where to save the metadata of the test run.
+expect_failure=0
+color_tests=0
+merge=0
+ignore_exit=0
+comments=0
+diag_string='#'
+while test $# -gt 0; do
+  case $1 in
+  --help) print_usage; exit $?;;
+  --version) echo "$me $scriptversion"; exit $?;;
+  --test-name) test_name=$2; shift;;
+  --log-file) log_file=$2; shift;;
+  --trs-file) trs_file=$2; shift;;
+  --color-tests) color_tests=$2; shift;;
+  --expect-failure) expect_failure=$2; shift;;
+  --enable-hard-errors) shift;; # No-op.
+  --merge) merge=1;;
+  --no-merge) merge=0;;
+  --ignore-exit) ignore_exit=1;;
+  --comments) comments=1;;
+  --no-comments) comments=0;;
+  --diagnostic-string) diag_string=$2; shift;;
+  --) shift; break;;
+  -*) usage_error "invalid option: '$1'";;
+  esac
+  shift
+done
+
+test $# -gt 0 || usage_error "missing test command"
+
+case $expect_failure in
+  yes) expect_failure=1;;
+    *) expect_failure=0;;
+esac
+
+if test $color_tests = yes; then
+  init_colors='
+    color_map["red"]="" # Red.
+    color_map["grn"]="" # Green.
+    color_map["lgn"]="" # Light green.
+    color_map["blu"]="" # Blue.
+    color_map["mgn"]="" # Magenta.
+    color_map["std"]=""     # No color.
+    color_for_result["ERROR"] = "mgn"
+    color_for_result["PASS"]  = "grn"
+    color_for_result["XPASS"] = "red"
+    color_for_result["FAIL"]  = "red"
+    color_for_result["XFAIL"] = "lgn"
+    color_for_result["SKIP"]  = "blu"'
+else
+  init_colors=''
+fi
+
+{
+  # FIXME: this usage loses the test program exit status.  We should
+  # probably rewrite the awk script to use the
+  #   expression | getline [var]
+  # idiom, which should allow us to obtain the final exit status from
+  # <expression> when closing it.
+  { test $merge -eq 0 || exec 2>&1; "$@"; echo $?; } \
+    | LC_ALL=C ${AM_TAP_AWK-awk} \
+        -v me="$me" \
+        -v test_script_name="$test_name" \
+        -v log_file="$log_file" \
+        -v trs_file="$trs_file" \
+        -v expect_failure="$expect_failure" \
+        -v merge="$merge" \
+        -v ignore_exit="$ignore_exit" \
+        -v comments="$comments" \
+        -v diag_string="$diag_string" \
+'
+# FIXME: the usages of "cat >&3" below could be optimized whne using
+# FIXME: GNU awk, and/on on systems that supports /dev/fd/.
+
+# Implementation note: in what follows, `result_obj` will be an
+# associative array that (partly) simulates a TAP result object
+# from the `TAP::Parser` perl module.
+
+## ----------- ##
+##  FUNCTIONS  ##
+## ----------- ##
+
+function fatal(msg)
+{
+  print me ": " msg | "cat >&3"
+  exit 1
+}
+
+function abort(where)
+{
+  fatal("internal error " where)
+}
+
+# Convert a boolean to a "yes"/"no" string.
+function yn(bool)
+{
+  return bool ? "yes" : "no";
+}
+
+function add_test_result(result)
+{
+  if (!test_results_index)
+    test_results_index = 0
+  test_results_list[test_results_index] = result
+  test_results_index += 1
+  test_results_seen[result] = 1;
+}
+
+# Whether the test script should be re-run by "make recheck".
+function must_recheck()
+{
+  for (k in test_results_seen)
+    if (k != "XFAIL" && k != "PASS" && k != "SKIP")
+      return 1
+  return 0
+}
+
+# Whether the content of the log file associated to this test should
+# be copied into the "global" test-suite.log.
+function copy_in_global_log()
+{
+  for (k in test_results_seen)
+    if (k != "PASS")
+      return 1
+  return 0
+}
+
+# FIXME: this can certainly be improved ...
+function get_global_test_result()
+{
+    if ("ERROR" in test_results_seen)
+      return "ERROR"
+    all_skipped = 1
+    for (k in test_results_seen)
+      if (k != "SKIP")
+        all_skipped = 0
+    if (all_skipped)
+      return "SKIP"
+    if ("FAIL" in test_results_seen || "XPASS" in test_results_seen)
+      return "FAIL"
+    return "PASS";
+}
+
+function stringify_result_obj(obj)
+{
+  if (obj["is_unplanned"] || obj["number"] != testno)
+    return "ERROR"
+
+  if (plan_seen == LATE_PLAN)
+    return "ERROR"
+
+  if (result_obj["directive"] == "TODO")
+    return obj["is_ok"] ? "XPASS" : "XFAIL"
+
+  if (result_obj["directive"] == "SKIP")
+    return obj["is_ok"] ? "SKIP" : COOKED_FAIL;
+
+  if (length(result_obj["directive"]))
+      abort("in function stringify_result_obj()")
+
+  return obj["is_ok"] ? COOKED_PASS : COOKED_FAIL
+}
+
+function decorate_result(result)
+{
+  color_name = color_for_result[result]
+  if (color_name)
+    return color_map[color_name] "" result "" color_map["std"]
+  # If we are not using colorized output, or if we do not know how
+  # to colorize the given result, we should return it unchanged.
+  return result
+}
+
+function report(result, details)
+{
+  if (result ~ /^(X?(PASS|FAIL)|SKIP|ERROR)/)
+    {
+      msg = ": " test_script_name
+      add_test_result(result)
+    }
+  else if (result == "#")
+    {
+      msg = " " test_script_name ":"
+    }
+  else
+    {
+      abort("in function report()")
+    }
+  if (length(details))
+    msg = msg " " details
+  # Output on console might be colorized.
+  print decorate_result(result) msg | "cat >&3";
+  # Log the result in the log file too, to help debugging (this is
+  # especially true when said result is a TAP error or "Bail out!").
+  print result msg;
+}
+
+function testsuite_error(error_message)
+{
+  report("ERROR", "- " error_message)
+}
+
+function handle_tap_result()
+{
+  details = result_obj["number"];
+  if (length(result_obj["description"]))
+    details = details " " result_obj["description"]
+
+  if (plan_seen == LATE_PLAN)
+    {
+      details = details " # AFTER LATE PLAN";
+    }
+  else if (result_obj["is_unplanned"])
+    {
+       details = details " # UNPLANNED";
+    }
+  else if (result_obj["number"] != testno)
+    {
+       details = sprintf("%s # OUT-OF-ORDER (expecting %d)",
+                         details, testno);
+    }
+  else if (result_obj["directive"])
+    {
+      details = details " # " result_obj["directive"];
+      if (length(result_obj["explanation"]))
+        details = details " " result_obj["explanation"]
+    }
+
+  report(stringify_result_obj(result_obj), details)
+}
+
+# `skip_reason` should be emprty whenever planned > 0.
+function handle_tap_plan(planned, skip_reason)
+{
+  planned += 0 # Avoid getting confused if, say, `planned` is "00"
+  if (length(skip_reason) && planned > 0)
+    abort("in function handle_tap_plan()")
+  if (plan_seen)
+    {
+      # Error, only one plan per stream is acceptable.
+      testsuite_error("multiple test plans")
+      return;
+    }
+  planned_tests = planned
+  # The TAP plan can come before or after *all* the TAP results; we speak
+  # respectively of an "early" or a "late" plan.  If we see the plan line
+  # after at least one TAP result has been seen, assume we have a late
+  # plan; in this case, any further test result seen after the plan will
+  # be flagged as an error.
+  plan_seen = (testno >= 1 ? LATE_PLAN : EARLY_PLAN)
+  # If testno > 0, we have an error ("too many tests run") that will be
+  # automatically dealt with later, so do not worry about it here.  If
+  # $plan_seen is true, we have an error due to a repeated plan, and that
+  # has already been dealt with above.  Otherwise, we have a valid "plan
+  # with SKIP" specification, and should report it as a particular kind
+  # of SKIP result.
+  if (planned == 0 && testno == 0)
+    {
+      if (length(skip_reason))
+        skip_reason = "- "  skip_reason;
+      report("SKIP", skip_reason);
+    }
+}
+
+function extract_tap_comment(line)
+{
+  # FIXME: verify there is not an off-by-one bug here.
+  if (index(line, diag_string) == 1)
+    {
+      # Strip leading `diag_string` from `line`.
+      # FIXME: verify there is not an off-by-one bug here.
+      line = substr(line, length(diag_string) + 1)
+      # And strip any leading and trailing whitespace left.
+      sub("^[ \t]*", "", line)
+      sub("[ \t]*$", "", line)
+      # Return what is left (if any).
+      return line;
+    }
+  return "";
+}
+
+# When this function is called, we know that line is a TAP result line,
+# so that it matches the (perl) RE "^(not )?ok\b".
+function setup_result_obj(line)
+{
+  # Get the result, and remove it from the line.
+  result_obj["is_ok"] = (substr(line, 1, 2) == "ok" ? 1 : 0)
+  sub("^(not )?ok[ \t]*", "", line)
+
+  # If the result has an explicit number, get it and strip it; otherwise,
+  # automatically assing the next progresive number to it.
+  if (line ~ /^[0-9]+$/ || line ~ /^[0-9]+[^a-zA-Z0-9_]/)
+    {
+      match(line, "^[0-9]+")
+      # The final `+ 0` is to normalize numbers with leading zeros.
+      result_obj["number"] = substr(line, 1, RLENGTH) + 0
+      line = substr(line, RLENGTH + 1)
+    }
+  else
+    {
+      result_obj["number"] = testno
+    }
+
+  if (plan_seen == LATE_PLAN)
+    # No further test results are acceptable after a "late" TAP plan
+    # has been seen.
+    result_obj["is_unplanned"] = 1
+  else if (plan_seen && testno > planned_tests)
+    result_obj["is_unplanned"] = 1
+  else
+    result_obj["is_unplanned"] = 0
+
+  # Strip trailing and leading whitespace.
+  sub("^[ \t]*", "", line)
+  sub("[ \t]*$", "", line)
+
+  # This will have to be corrected if we have a "TODO"/"SKIP" directive.
+  result_obj["description"] = line
+  result_obj["directive"] = ""
+  result_obj["explanation"] = ""
+
+  # TODO: maybe we should allow a way to escape "#"?
+  if (index(line, "#") == 0)
+    return # No possible directive, nothing more to do.
+
+  # Directives are case-insensitive.
+  rx = "[ \t]*#[ \t]*([tT][oO][dD][oO]|[sS][kK][iI][pP])[ \t]*"
+
+  # See whether we have the directive, and if yes, where.
+  pos = match(line, rx "$")
+  if (!pos)
+    pos = match(line, rx "[^a-zA-Z0-9_]")
+
+  # If there was no TAP directive, we have nothing more to do.
+  if (!pos)
+    return
+
+  # Strip the directive and its explanation (if any) from the test
+  # description.
+  result_obj["description"] = substr(line, 1, pos - 1)
+  # Now remove the test description from the line, that has been dealt
+  # with already.
+  line = substr(line, pos)
+  # Strip the directive, and save its value (normalized to upper case).
+  sub("^[ \t]*#[ \t]*", "", line)
+  result_obj["directive"] = toupper(substr(line, 1, 4))
+  line = substr(line, 5)
+  # Now get the explanation for the directive (if any), with leading
+  # and trailing whitespace removed.
+  sub("^[ \t]*", "", line)
+  sub("[ \t]*$", "", line)
+  result_obj["explanation"] = line
+}
+
+function get_test_exit_message(status)
+{
+  if (status == 0)
+    return ""
+  if (status !~ /^[1-9][0-9]*$/)
+    abort("getting exit status")
+  if (status < 127)
+    exit_details = ""
+  else if (status == 127)
+    exit_details = " (command not found?)"
+  else if (status >= 128 && status <= 255)
+    exit_details = sprintf(" (terminated by signal %d?)", status - 128)
+  else if (status >= 256)
+    exit_details = " (abnormal termination)"
+  return sprintf("exited with status %d%s", status, exit_details)
+}
+
+function write_test_results()
+{
+  print ":global-test-result: " get_global_test_result() > trs_file
+  print ":recheck: "  yn(must_recheck()) > trs_file
+  print ":copy-in-global-log: " yn(copy_in_global_log()) > trs_file
+  for (i = 0; i < test_results_index; i += 1)
+    print ":test-result: " test_results_list[i] > trs_file
+  close(trs_file);
+}
+
+BEGIN {
+
+## ------- ##
+##  SETUP  ##
+## ------- ##
+
+'"$init_colors"'
+
+# Properly initialized once the TAP plan is seen.
+planned_tests = 0
+
+COOKED_PASS = expect_failure ? "XPASS": "PASS";
+COOKED_FAIL = expect_failure ? "XFAIL": "FAIL";
+
+# Enumeration-like constants to remember which kind of plan (if any)
+# has been seen.  It is important that NO_PLAN evaluates "false" as
+# a boolean.
+NO_PLAN = 0
+EARLY_PLAN = 1
+LATE_PLAN = 2
+
+testno = 0     # Number of test results seen so far.
+bailed_out = 0 # Whether a "Bail out!" directive has been seen.
+
+# Whether the TAP plan has been seen or not, and if yes, which kind
+# it is ("early" is seen before any test result, "late" otherwise).
+plan_seen = NO_PLAN
+
+## --------- ##
+##  PARSING  ##
+## --------- ##
+
+is_first_read = 1
+
+while (1)
+  {
+    # Involutions required so that we are able to read the exit status
+    # from the last input line.
+    st = getline
+    if (st < 0) # I/O error.
+      fatal("I/O error while reading from input stream")
+    else if (st == 0) # End-of-input
+      {
+        if (is_first_read)
+          abort("in input loop: only one input line")
+        break
+      }
+    if (is_first_read)
+      {
+        is_first_read = 0
+        nextline = $0
+        continue
+      }
+    else
+      {
+        curline = nextline
+        nextline = $0
+        $0 = curline
+      }
+    # Copy any input line verbatim into the log file.
+    print
+    # Parsing of TAP input should stop after a "Bail out!" directive.
+    if (bailed_out)
+      continue
+
+    # TAP test result.
+    if ($0 ~ /^(not )?ok$/ || $0 ~ /^(not )?ok[^a-zA-Z0-9_]/)
+      {
+        testno += 1
+        setup_result_obj($0)
+        handle_tap_result()
+      }
+    # TAP plan (normal or "SKIP" without explanation).
+    else if ($0 ~ /^1\.\.[0-9]+[ \t]*$/)
+      {
+        # The next two lines will put the number of planned tests in $0.
+        sub("^1\\.\\.", "")
+        sub("[^0-9]*$", "")
+        handle_tap_plan($0, "")
+        continue
+      }
+    # TAP "SKIP" plan, with an explanation.
+    else if ($0 ~ /^1\.\.0+[ \t]*#/)
+      {
+        # The next lines will put the skip explanation in $0, stripping
+        # any leading and trailing whitespace.  This is a little more
+        # tricky in truth, since we want to also strip a potential leading
+        # "SKIP" string from the message.
+        sub("^[^#]*#[ \t]*(SKIP[: \t][ \t]*)?", "")
+        sub("[ \t]*$", "");
+        handle_tap_plan(0, $0)
+      }
+    # "Bail out!" magic.
+    else if ($0 ~ /^Bail out!/)
+      {
+        bailed_out = 1
+        # Get the bailout message (if any), with leading and trailing
+        # whitespace stripped.  The message remains stored in `$0`.
+        sub("^Bail out![ \t]*", "");
+        sub("[ \t]*$", "");
+        # Format the error message for the
+        bailout_message = "Bail out!"
+        if (length($0))
+          bailout_message = bailout_message " " $0
+        testsuite_error(bailout_message)
+      }
+    # Maybe we have too look for dianogtic comments too.
+    else if (comments != 0)
+      {
+        comment = extract_tap_comment($0);
+        if (length(comment))
+          report("#", comment);
+      }
+  }
+
+## -------- ##
+##  FINISH  ##
+## -------- ##
+
+# A "Bail out!" directive should cause us to ignore any following TAP
+# error, as well as a non-zero exit status from the TAP producer.
+if (!bailed_out)
+  {
+    if (!plan_seen)
+      {
+        testsuite_error("missing test plan")
+      }
+    else if (planned_tests != testno)
+      {
+        bad_amount = testno > planned_tests ? "many" : "few"
+        testsuite_error(sprintf("too %s tests run (expected %d, got %d)",
+                                bad_amount, planned_tests, testno))
+      }
+    if (!ignore_exit)
+      {
+        # Fetch exit status from the last line.
+        exit_message = get_test_exit_message(nextline)
+        if (exit_message)
+          testsuite_error(exit_message)
+      }
+  }
+
+write_test_results()
+
+exit 0
+
+} # End of "BEGIN" block.
+'
+
+# TODO: document that we consume the file descriptor 3 :-(
+} 3>&1 >"$log_file" 2>&1
+
+test $? -eq 0 || fatal "I/O or internal error"
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/t/test-lib.sh b/t/test-lib.sh
index df25f17..36a082c 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -854,7 +854,11 @@ test_done () {
                        say "1..$test_count"
                fi
 
-               exit 1 ;;
+               if test -z "$HARNESS_ACTIVE"; then
+                       exit 1
+               else
+                       exit 0
+               fi ;;
 
        esac
 }
-- 
1.7.2.3


reply via email to

[Prev in Thread] Current Thread [Next in Thread]