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: <5c980aaaf3501b28c02139627396d458f6f50313.1314032828.git.stefano.lattar...@gmail.com> From: Stefano Lattarini <[email protected]> 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 $@+ $@ 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@@], [[email protected]]) +AC_PREREQ([2.62]) +AC_INIT([git], + m4_esyscmd([cat GIT-VERSION-FILE | sed 's/.*= *//' | tr -d '\n']), + [[email protected]]) 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 + +@am_protect@ifeq ($(SH_LOG_DRIVER),) +@am_protect@$(error invalid test harness implementation $(t-harness)) +@am_protect@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 => \@_, 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 <[email protected]> or send patches to +# <[email protected]>. + +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"]="[0;31m" # Red. + color_map["grn"]="[0;32m" # Green. + color_map["lgn"]="[1;32m" # Light green. + color_map["blu"]="[1;34m" # Blue. + color_map["mgn"]="[0;35m" # Magenta. + color_map["std"]="[m" # 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
