#
#
# rename "testsuite.lua"
# to "lua-testsuite.lua"
#
# add_file "tester-plaf.hh"
# content [19ccb35a02a73e028ba8e80d027213b0eaf52523]
#
# add_file "unix/tester-plaf.cc"
# content [c97649b0639fa5153f5386acc7910b23e364f06e]
#
# add_file "win32/tester-plaf.cc"
# content [636d02af62c13568df4a0e0886e7cc2639df0d51]
#
# patch "Makefile.am"
# from [9c3489048d11104d7f82f84db5204d060b313cb5]
# to [877e36a8c93c0975e824d2cbbde61491bf7182b8]
#
# patch "tester.cc"
# from [5b178198e6da69bbcf66b2b9dbeace131e0838db]
# to [5d4f12816d5e6c83f0d96c39c0db03839b693765]
#
============================================================
--- tester-plaf.hh 19ccb35a02a73e028ba8e80d027213b0eaf52523
+++ tester-plaf.hh 19ccb35a02a73e028ba8e80d027213b0eaf52523
@@ -0,0 +1,44 @@
+#ifndef __TESTER_PLAF_HH__
+#define __TESTER_PLAF_HH__
+
+// Copyright (C) 2007 Zack Weinberg
+//
+// This program is made available under the GNU GPL version 2.0 or
+// greater. See the accompanying file COPYING for details.
+//
+// This program is distributed WITHOUT ANY WARRANTY; without even the
+// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+// PURPOSE.
+
+// this describes functions to be found, alternatively, in win32/* or unix/*
+// directories and used only by the tester.
+
+#include // time_t and pid_t
+
+void make_accessible(std::string const & name);
+time_t get_last_write_time(char const * name);
+void do_copy_file(std::string const & from, std::string const & to);
+void set_env(char const * var, char const * val);
+void unset_env(char const * var);
+int do_umask(int mask);
+char * make_temp_dir();
+bool running_as_root();
+
+struct lua_State;
+pid_t run_one_test_in_child(std::string const & testname,
+ std::string const & testdir,
+ lua_State * st,
+ std::string const & argv0,
+ std::string const & testfile,
+ std::string const & firstdir);
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
+
+#endif
+
============================================================
--- unix/tester-plaf.cc c97649b0639fa5153f5386acc7910b23e364f06e
+++ unix/tester-plaf.cc c97649b0639fa5153f5386acc7910b23e364f06e
@@ -0,0 +1,310 @@
+// Tester-specific platform interface glue, Unix version.
+
+#include "base.hh"
+#include "sanity.hh"
+#include "platform.hh"
+#include "tester-plaf.hh"
+
+#include
+#include
+#include
+#include
+
+using std::string;
+
+void make_accessible(string const & name)
+{
+ struct stat st;
+ if (stat(name.c_str(), &st) != 0)
+ {
+ const int err = errno;
+ E(false, F("stat(%s) failed: %s") % name % os_strerror(err));
+ }
+
+ mode_t new_mode = st.st_mode;
+ if (S_ISDIR(st.st_mode))
+ new_mode |= S_IEXEC;
+ new_mode |= S_IREAD | S_IWRITE;
+
+ if (chmod(name.c_str(), new_mode) != 0)
+ {
+ const int err = errno;
+ E(false, F("chmod(%s) failed: %s") % name % os_strerror(err));
+ }
+}
+
+time_t get_last_write_time(char const * name)
+{
+ struct stat st;
+ if (stat(name, &st) != 0)
+ {
+ const int err = errno;
+ E(false, F("stat(%s) failed: %s") % name % os_strerror(err));
+ }
+
+ return st.st_mtime;
+}
+
+void do_copy_file(string const & from, string const & to)
+{
+ char buf[32768];
+ int ifd, ofd;
+ ifd = open(from.c_str(), O_RDONLY);
+ const int err = errno;
+ E(ifd >= 0, F("open %s: %s") % from % os_strerror(err));
+ struct stat st;
+ st.st_mode = 0666; // sane default if fstat fails
+ fstat(ifd, &st);
+ ofd = open(to.c_str(), O_WRONLY|O_CREAT|O_EXCL, st.st_mode);
+ if (ofd < 0)
+ {
+ const int err = errno;
+ close(ifd);
+ E(false, F("open %s: %s") % to % os_strerror(err));
+ }
+
+ ssize_t nread, nwrite;
+ int ndead;
+ for (;;)
+ {
+ nread = read(ifd, buf, 32768);
+ if (nread < 0)
+ goto read_error;
+ if (nread == 0)
+ break;
+
+ nwrite = 0;
+ ndead = 0;
+ do
+ {
+ ssize_t nw = write(ofd, buf + nwrite, nread - nwrite);
+ if (nw < 0)
+ goto write_error;
+ if (nw == 0)
+ ndead++;
+ if (ndead == 4)
+ goto spinning;
+ nwrite += nw;
+ }
+ while (nwrite < nread);
+ }
+ close(ifd);
+ close(ofd);
+ return;
+
+ read_error:
+ {
+ int err = errno;
+ close(ifd);
+ close(ofd);
+ E(false, F("read error copying %s to %s: %s")
+ % from % to % os_strerror(err));
+ }
+ write_error: {
+ int err = errno;
+ close(ifd);
+ close(ofd);
+ E(false, F("write error copying %s to %s: %s")
+ % from % to % os_strerror(err));
+ }
+ spinning:
+ {
+ close(ifd);
+ close(ofd);
+ E(false, F("abandoning copy of %s to %s after four zero-length writes")
+ % from % to);
+ }
+}
+
+void set_env(char const * var, char const * val)
+{
+#if defined HAVE_SETENV
+ setenv(var, val, 1);
+#elif defined HAVE_PUTENV
+ // note: this leaks memory, but the tester is short lived so it probably
+ // doesn't matter much.
+ string * tempstr = new string(var);
+ tempstr->append("=");
+ tempstr->append(val);
+ putenv(const_cast(tempstr->c_str()));
+#else
+#error set_env needs to be ported to this platform
+#endif
+}
+
+void unset_env(char const * var)
+{
+#if defined HAVE_UNSETENV
+ unsetenv(var);
+#else
+#error unset_env needs to be ported to this platform
+#endif
+}
+
+// This function cannot fail, but the Windows version of this function
+// always returns -1 to indicate no system support for the operation.
+// Therefore the argument and return value are signed.
+int do_umask(int mask)
+{
+ return umask(mask);
+}
+
+char * make_temp_dir()
+{
+ char const * parent;
+ parent = getenv("TMPDIR");
+ if (parent == 0)
+ parent = getenv("TEMP");
+ if (parent == 0)
+ parent = getenv("TMP");
+ if (parent == 0)
+ parent = "/tmp";
+
+ char * templ = new char[strlen(parent) + sizeof "/mtXXXXXX"];
+ strcpy(templ, parent);
+ strcat(templ, "/mtXXXXXX");
+
+ char * result;
+
+ // mkdtemp is not available on all systems.
+#ifdef HAVE_MKDTEMP
+
+ result = mkdtemp(templ);
+ I(result == templ);
+ return templ;
+
+#else
+
+ // Typical use of mktemp() risks the file being created by someone else in
+ // between when the name is chosen and the file is opened. However, use
+ // of mktemp() to pick a *directory* name is safe, because mkdir() will
+ // not create a directory if anything already exists by that name - even a
+ // dangling symlink. Thus we can simply loop until we find a suitable
+ // name. There IS a very small risk that we loop endlessly, but that's
+ // under extreme conditions, and the problem is likely to really be
+ // elsewhere... as a backstop, we limit iterations to the smaller of
+ // 10000 and TMP_MAX.
+
+ unsigned int cycles = 0, limit = 10000;
+#ifdef TMP_MAX
+ if (TMP_MAX > 0 && TMP_MAX < limit)
+ limit = TMP_MAX;
+#endif
+
+ char * tmpdir = new char[strlen(templ) + 1];
+ for (;;)
+ {
+ strcpy(tmpdir, templ);
+ result = mktemp(tmpdir);
+ E(result, F("mktemp(%s) failed: %s") % tmpdir % os_strerror(errno));
+ I(result == tmpdir);
+
+ if (mkdir(tmpdir, 0700) == 0)
+ {
+ strcpy(templ, tmpdir);
+ delete [] tmpdir;
+ return templ;
+ }
+
+ E(errno == EEXIST,
+ F("mkdir(%s) failed: %s") % tmpdir % os_strerror(errno));
+
+ cycles++;
+ E(cycles < limit,
+ F("%d temporary names are all in use") % limit);
+ }
+
+#endif
+}
+
+
+bool running_as_root()
+{
+ return !geteuid();
+}
+
+#include "lua/lua.h"
+#include "lua/lualib.h"
+#include "lua/lauxlib.h"
+
+// Spawn a child to run the single test TESTNAME. Some args are not used on
+// this platform.
+pid_t
+run_one_test_in_child(string const & testname,
+ string const & testdir,
+ lua_State * st,
+ string const & /* argv0 */,
+ string const & /* testfile */,
+ string const & /* firstdir */)
+{
+ // Make sure there is no pending buffered output before forking, or it
+ // may be doubled.
+ fflush(0);
+
+ pid_t child = fork();
+ if (child != 0)
+ return child;
+
+ // From this point on we are in the child process.
+ // Until we have entered the test directory and re-opened fds 0-2 it is
+ // not safe to throw exceptions or call any of the diagnostic routines.
+ // Hence we use bare OS primitives and call _exit() if any of them fail.
+ // It is safe to assume that close() will not fail.
+ if (chdir(testdir.c_str()) != 0) _exit(127);
+
+ close(0);
+ if (open("/dev/null", O_RDONLY) != 0) _exit(126);
+
+ close(1);
+ if (open("tester.log", O_WRONLY|O_CREAT|O_EXCL, 0666) != 1) _exit(125);
+ if (dup2(1, 2) == -1) _exit(124);
+
+ // We can now safely use stdio, exceptions, and the normal diagnostic
+ // routines. However, as caller is not expecting it, we must not leave
+ // this function by any means save exit(), hence we duplicate all the
+ // outermost catch clauses from main().
+
+ int retcode;
+ try
+ {
+ lua_getglobal(st, "run_one_test");
+ I(lua_isfunction(st, -1));
+
+ lua_pushstring(st, testname.c_str());
+ lua_call(st, 1, 1);
+ I(lua_isnumber(st, -1));
+
+ retcode = lua_tointeger(st, -1);
+ }
+ catch (informative_failure & e)
+ {
+ P(F("%s\n") % e.what());
+ retcode = 1;
+ }
+ catch (std::logic_error & e)
+ {
+ P(F("Invariant failure: %s\n") % e.what());
+ retcode = 3;
+ }
+ catch (std::exception & e)
+ {
+ P(F("Uncaught exception: %s") % e.what());
+ retcode = 3;
+ }
+ catch (...)
+ {
+ P(F("Uncaught exception of unknown type"));
+ retcode = 3;
+ }
+
+ lua_close(st);
+ exit(retcode);
+}
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
============================================================
--- win32/tester-plaf.cc 636d02af62c13568df4a0e0886e7cc2639df0d51
+++ win32/tester-plaf.cc 636d02af62c13568df4a0e0886e7cc2639df0d51
@@ -0,0 +1,139 @@
+// Tester-specific platform interface glue, Windows version.
+
+#define WIN32_LEAN_AND_MEAN // we don't need the GUI interfaces
+
+#include "base.hh"
+#include "tester-plaf.hh"
+#include "sanity.hh"
+
+#include
+
+void make_accessible(string const & name)
+{
+ DWORD attrs = GetFileAttributes(name.c_str());
+ E(attrs != INVALID_FILE_ATTRIBUTES,
+ F("GetFileAttributes(%s) failed: %s") % name % os_strerror(GetLastError()));
+
+ E(SetFileAttributes(name.c_str(), attrs & ~FILE_ATTRIBUTE_READONLY),
+ F("SetFileAttributes(%s) failed: %s") % name % os_strerror(GetLastError()));
+}
+
+time_t get_last_write_time(char const * name)
+{
+ HANDLE h = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL,
+ OPEN_EXISTING, 0, NULL);
+ E(h != INVALID_HANDLE_VALUE,
+ F("CreateFile(%s) failed: %s") % name % os_strerror(GetLastError()));
+
+ FILETIME ft;
+ E(GetFileTime(h, NULL, NULL, &ft),
+ F("GetFileTime(%s) failed: %s") % name % os_strerror(GetLastError()));
+
+ CloseHandle(h);
+
+ // A FILETIME is a 64-bit quantity (represented as a pair of DWORDs)
+ // representing the number of 100-nanosecond intervals elapsed since
+ // 12:00 AM, January 1, 1601 UTC. A time_t is the same as it is for
+ // Unix: seconds since 12:00 AM, January 1, 1970 UTC. The offset is
+ // taken verbatim from MSDN.
+ LONGLONG ft64 = ((LONGLONG)ft.dwHighDateTime) << 32 + ft.dwLowDateTime;
+ return (time_t)((ft64/10000000) - 11644473600LL);
+}
+
+void do_copy_file(string const & from, string const & to)
+{
+ // For once something is easier with Windows.
+ E(CopyFile(from.c_str(), to.c_str(), true),
+ F("copy %s to %s: %s") % from % to % os_strerror(GetLastError()));
+}
+
+
+void set_env(char const * var, char const * val)
+{
+ SetEnvironmentVariable(var, val);
+}
+
+void unset_env(char const * var)
+{
+ SetEnvironmentVariable(var, 0);
+}
+
+int do_umask(int /* mask */)
+{
+ return -1; // not a meaningful operation on Windows
+}
+
+char * make_temp_dir()
+{
+ char dir[PATH_MAX];
+
+ // GetTempFileName wants 14 characters at the end of the path.
+ {
+ DWORD ret = GetTempPath(PATH_MAX - 14, dir);
+ E(ret > 0 && ret <= PATH_MAX - 14,
+ F("GetTempPath failed: %s") % os_strerror(GetLastError()));
+ }
+
+ // If the third argument to GetTempFileName is zero, it will create a
+ // file, which is not what we want.
+ UINT base = GetTickCount();
+ char * name = new char[PATH_MAX];
+ for (UINT i = 0; i < 65535; i++)
+ {
+ if (base + i == 0)
+ continue;
+
+ E(GetTempFileName(dir, "MTN", base + i, name) != 0,
+ F("GetTempFileName failed: %s") % os_strerror(GetLastError()));
+
+ if (CreateDirectory(name, NULL))
+ return name;
+
+ E(GetLastError() == ERROR_ALREADY_EXISTS,
+ F("CreateDirectory(%s) failed: %s") % name % GetLastError());
+ }
+ E(false, F("All temporary directory names are already in use."));
+}
+
+bool running_as_root()
+{
+ // ??? check for privileges (what the test suite cares about is being able
+ // to create files it cannot write to - may not be impossible for any
+ // privileged account in Windows)
+ return false;
+}
+
+
+pid_t run_one_test_in_child(string const & testname,
+ string const & testdir,
+ lua_State * /* st */,
+ string const & argv0,
+ string const & testfile,
+ string const & firstdir)
+{
+ // The bulk of the work is done in main(), -r case, q.v.
+ char const * argv[6];
+ argv[0] = argv0.c_str();
+ argv[1] = "-r";
+ argv[2] = testfile.c_str();
+ argv[3] = firstdir.c_str();
+ argv[4] = testname.c_str();
+ argv[5] = 0;
+
+ change_current_working_dir(testdir);
+ pid_t child = process_spawn_redirected("NUL:",
+ "tester.log",
+ "tester.log",
+ argv);
+ change_current_working_dir(run_dir);
+ return child;
+}
+
+
+// Local Variables:
+// mode: C++
+// fill-column: 76
+// c-file-style: "gnu"
+// indent-tabs-mode: nil
+// End:
+// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
============================================================
--- Makefile.am 9c3489048d11104d7f82f84db5204d060b313cb5
+++ Makefile.am 877e36a8c93c0975e824d2cbbde61491bf7182b8
@@ -276,15 +276,15 @@ UNIX_PLATFORM_SOURCES = \
unix/read_password.cc unix/get_system_flavour.cc \
unix/process.cc unix/terminal.cc unix/inodeprint.cc \
unix/fs.cc unix/make_io_binary.cc unix/os_strerror.cc \
- unix/cputime.cc unix/ssh_agent_platform.cc \
- unix/ssh_agent_platform.hh
+ unix/cputime.cc unix/ssh_agent_platform.cc \
+ unix/ssh_agent_platform.hh unix/tester-plaf.cc
WIN32_PLATFORM_SOURCES = \
win32/read_password.cc win32/get_system_flavour.cc \
win32/process.cc win32/terminal.cc win32/inodeprint.cc \
- win32/fs.cc win32/make_io_binary.cc win32/os_strerror.cc \
- win32/cputime.cc win32/ssh_agent_platform.cc \
- win32/ssh_agent_platform.hh
+ win32/fs.cc win32/make_io_binary.cc win32/os_strerror.cc \
+ win32/cputime.cc win32/ssh_agent_platform.cc \
+ win32/ssh_agent_platform.hh win32/tester-plaf.cc
# these files contain unit tests
UNIT_TEST_SOURCES = \
@@ -343,7 +343,7 @@ noinst_LIBRARIES = libplatform.a lib3rdp
txt2c_SOURCES = txt2c.cc
noinst_LIBRARIES = libplatform.a lib3rdparty.a
-libplatform_a_SOURCES = platform.hh
+libplatform_a_SOURCES = platform.hh tester-plaf.hh
lib3rdparty_a_SOURCES = $(BOOST_SANDBOX_SOURCES) \
$(BOTAN_SOURCES) \
$(IDNA_SOURCES) \
@@ -537,20 +537,83 @@ monotone-$(PACKAGE_VERSION).dmg: monoton
"$@" -format UDZO -scrub -imagekey zlib-level=9 \
-volname monotone-$(PACKAGE_VERSION)
-# testsuite stuff (could this possibly be more ugly?)
+# testsuite stuff (could this possibly be more ugly?) To get
+# parallelism, we cannot use Automake's TESTS feature at all. The
+# shell script embedded in the 'check-local' rule is partially
+# borrowed from automake 1.9's check.am
+check-local: tester_tests.status unit_tests.status lua_tests.status
+ @all=0; failed=0; error=0; \
+ for f in $^; do \
+ all=`expr $$all + 1`; \
+ if test -f $$f; then \
+ s=`cat $$f`; \
+ if test "$$s" = 0; then \
+ :; \
+ elif test "$$s" = 1; then \
+ failed=`expr $$failed + 1`; \
+ else \
+ error=`expr $$error + 1`; \
+ fi; \
+ else \
+ error=`expr $$error + 1`; \
+ fi; \
+ done; \
+ if test "$$failed" = 0 && test "$$error" = 0; then \
+ exit=0; \
+ report=""; \
+ banner="All $$all tests passed"; \
+ else \
+ exit=1; \
+ report="Please report to $(PACKAGE_BUGREPORT)"; \
+ if test "$$error" = 0; then \
+ banner="$$failed of $$all tests failed"; \
+ elif test "$$failed" = 0; then \
+ banner="$$error of $$all tests malfunctioned"; \
+ else \
+ banner="$$failed of $$all tests failed and $$error malfunctioned"; \
+ fi; \
+ fi; \
+ rl=`echo $$report | wc -c`; \
+ bl=`echo $$banner | wc -c`; \
+ if test $$rl -gt $$bl; then \
+ dashes=`echo "$$report" | sed s/./=/g`; \
+ else \
+ dashes=`echo "$$banner" | sed s/./=/g`; \
+ fi; \
+ echo $$dashes; \
+ echo $$banner; \
+ test -z "$$report" || echo "$$report"; \
+ exit $$exit
-check_PROGRAMS = unit_tester tester
-TESTS = run_unit_tests run_tester_tests run_lua_tests
+run_%_tests: Makefile
+ { echo '#!/bin/sh'; \
+ echo 'PATH=$(top_builddir):$$PATH'; \
+ echo '$(top_builddir)/tester $(srcdir)/$*-testsuite.lua "$$@"'; \
+ echo 'echo $$? > $*_tests.status'; \
+ echo 'exit 0'; } > $@
+ chmod 755 $@
+%_tests.status: run_%_tests %-testsuite.lua tester FORCE
+ ./run_$*_tests
-TESTS_ENVIRONMENT=AUTOTEST_PATH="."
+unit_tests.status : unit_tester
+lua_tests.status : mtn
+check_PROGRAMS = unit_tester tester
+# We want the tests re-run even if the .status files already exist.
+# .PHONY does not work for that (bad interaction with pattern rules),
+# but the FORCE hack does.
+.PHONY: check-local FORCE
+FORCE:
+
mostlyclean-local:
rm -rf tester_dir unit-tests package_full_revision.txt
-DISTCLEANFILES = mt-stdint.h xgettext.opts pch-build.hh.gch.dep \
- run_tester_tests run_lua_tests run_unit_tests
+DISTCLEANFILES = mt-stdint.h xgettext.opts pch-build.hh.gch.dep \
+ run_tester_tests run_unit_tests run_unit_tests \
+ report_tester_tests report_unit_tests report_lua_tests \
+ tester-tests.status unit_tests.status lua_tests.status
# distcheck stuff
@@ -573,24 +636,6 @@ $(srcdir)/package.m4: $(top_srcdir)/conf
echo 'm4_define([AT_PACKAGE_BUGREPORT], address@hidden@])'; \
} >$(srcdir)/package.m4
-run_lua_tests: Makefile
- echo '#!/bin/sh' > $@ ; \
- echo 'PATH=$(top_builddir):$$PATH' >> $@ ; \
- echo 'exec $(top_builddir)/tester $(srcdir)/testsuite.lua "$$@"' >> $@ ; \
- chmod 755 $@
-
-run_tester_tests: Makefile
- echo '#!/bin/sh' > $@ ; \
- echo 'PATH=$(top_builddir):$$PATH' >> $@ ; \
- echo 'exec $(top_builddir)/tester $(srcdir)/tester-testsuite.lua "$$@"' >> $@ ; \
- chmod 755 $@
-
-run_unit_tests: Makefile
- echo '#!/bin/sh' > $@ ; \
- echo 'PATH=$(top_builddir):$$PATH' >> $@ ; \
- echo 'exec $(top_builddir)/tester $(srcdir)/unit-testsuite.lua "$$@"' >> $@ ; \
- chmod 755 $@
-
# we generate some source files to copy data into the executable
# note that the only things that should go in BUILT_SOURCES are things
# that need to be generated early on 'make all'; this is _not_ true of
============================================================
--- tester.cc 5b178198e6da69bbcf66b2b9dbeace131e0838db
+++ tester.cc 5d4f12816d5e6c83f0d96c39c0db03839b693765
@@ -1,60 +1,17 @@
#include "base.hh"
-#include "lua.h"
-#include "lualib.h"
-#include "lauxlib.h"
-
#include "lua.hh"
#include "platform.hh"
+#include "tester-plaf.hh"
#include "sanity.hh"
#include "option.hh"
-#include
-#include
-#include
-#include