# # # 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 -#include -#include +using std::string; +using std::map; +using std::vector; -/* for mkdir() */ -#include -#include - -#ifdef WIN32 -/* For _mktemp() */ -#include -#define mktemp(t) _mktemp(t) -/* For _mkdir() */ -#include -#define mkdir(d,m) _mkdir(d) -#endif - -#ifdef WIN32 -#define WIN32_LEAN_AND_MEAN // we don't need the GUI interfaces -#include -#else -#include -#include -#include -#endif - -#ifdef WIN32 -#define NULL_DEVICE "NUL:" -#else -#define NULL_DEVICE "/dev/null" -#endif - // defined in testlib.c, generated from testlib.lua extern char const testlib_constant[]; -using std::string; -using std::map; -using std::memcpy; -using std::getenv; -using std::exit; -using std::make_pair; -using std::vector; -using std::time_t; - // Lua uses the c i/o functions, so we need to too. struct tester_sanity : public sanity { @@ -70,189 +27,6 @@ sanity & global_sanity = real_sanity; tester_sanity real_sanity; sanity & global_sanity = real_sanity; - -void make_accessible(string const &name) -{ -#ifdef WIN32 - - 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())); - -#else - - 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)); - - } - -#endif -} - -time_t get_last_write_time(string const & name) -{ -#ifdef WIN32 - - HANDLE h = CreateFile(name.c_str(), 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); - -#else - - 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)); - } - - return st.st_mtime; - -#endif -} - -void do_copy_file(string const & from, string const & to) -{ -#ifdef WIN32 - // 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())); - -#else - 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); - } - -#endif -} - - -void set_env(char const * var, char const * val) -{ -#if defined(WIN32) - SetEnvironmentVariable(var, val); -#elif 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(WIN32) - SetEnvironmentVariable(var, 0); -#elif defined(HAVE_UNSETENV) - unsetenv(var); -#else -#error unset_env needs to be ported to this platform -#endif -} - string basename(string const & s) { string::size_type sep = s.rfind('/'); @@ -274,62 +48,6 @@ string dirname(string const & s) return s.substr(0, sep); } -#if !defined(HAVE_MKDTEMP) -static char * _impl_mkdtemp(char * templ) -{ - char * tmpdir = new char[strlen(templ) + 1]; - char * result = 0; - - /* There's a possibility that the name returned by mktemp() will already - be created by someone else, a typical race condition. However, since - mkdir() will not clobber an already existing file or directory, 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... */ - do - { - strcpy(tmpdir, templ); - result = mktemp(tmpdir); - if (result && mkdir(tmpdir, 0700) != 0) - { - result = 0; - } - } - while(!result && errno == EEXIST); - - if (result) - { - strcpy(templ, result); - result = templ; - } - - delete [] tmpdir; - return result; -} - -#define mkdtemp _impl_mkdtemp -#endif - -char * do_mkdtemp(char const * parent) -{ - char * tmpdir = new char[strlen(parent) + sizeof "/mtXXXXXX"]; - - strcpy(tmpdir, parent); - strcat(tmpdir, "/mtXXXXXX"); - - char * result = mkdtemp(tmpdir); - const int err = errno; - - E(result != 0, - F("mkdtemp(%s) failed: %s") % tmpdir % os_strerror(err)); - I(result == tmpdir); - return tmpdir; -} - -#if !defined(HAVE_MKDTEMP) -#undef mkdtemp -#endif - map orig_env_vars; static string argv0; @@ -492,19 +210,41 @@ void do_copy_recursive(string const & fr do_copy_file(from, to); } +// For convenience in calling from Lua (which has no syntax for writing +// octal numbers) this function takes a three-digit *decimal* number and +// treats each digit as octal. For example, 777 (decimal) is converted to +// 0777 (octal) for the system call. Note that the system always forces the +// high three bits of the supplied mode to zero; i.e. it is impossible to +// have the setuid, setgid, or sticky bits on in the process umask. +// Therefore, there is no point accepting arguments higher than 777. LUAEXT(posix_umask, ) { -#ifdef WIN32 - lua_pushnil(L); - return 1; -#else - unsigned int from = (unsigned int)luaL_checknumber(L, -1); - mode_t mask = 64*((from / 100) % 10) + 8*((from / 10) % 10) + (from % 10); - mode_t oldmask = umask(mask); - int res = 100*(oldmask/64) + 10*((oldmask/8) % 8) + (oldmask % 8); - lua_pushnumber(L, res); - return 1; -#endif + unsigned int decmask = (unsigned int)luaL_checknumber(L, -1); + E(decmask <= 777, + F("invalid argument %d to umask") % decmask); + + unsigned int a = decmask / 100 % 10; + unsigned int b = decmask / 10 % 10; + unsigned int c = decmask / 1 % 10; + + E(a <= 7 && b <= 7 && c <= 7, + F("invalid octal number %d in umask") % decmask); + + int oldmask = do_umask((a*8 + b)*8 + c); + if (oldmask == -1) + { + lua_pushnil(L); + return 1; + } + else + { + a = ((unsigned int)oldmask) / 64 % 8; + b = ((unsigned int)oldmask) / 8 % 8; + c = ((unsigned int)oldmask) / 1 % 8; + + lua_pushinteger(L, (a*10 + b)*10 + c); + return 1; + } } LUAEXT(chdir, ) @@ -593,16 +333,8 @@ LUAEXT(make_temp_dir, ) { try { - char const * parent; - parent = getenv("TMPDIR"); - if (parent == 0) - parent = getenv("TEMP"); - if (parent == 0) - parent = getenv("TMP"); - if (parent == 0) - parent = "/tmp"; + char * tmpdir = make_temp_dir(); - char * tmpdir = do_mkdtemp(parent); lua_pushstring(L, tmpdir); delete [] tmpdir; return 1; @@ -781,14 +513,9 @@ LUAEXT(require_not_root, ) LUAEXT(require_not_root, ) { -#ifdef WIN32 - bool running_as_root = false; -#else - bool running_as_root = !geteuid(); -#endif // E() doesn't work here, I just get "warning: " in the // output. Why? - if (running_as_root) + if (running_as_root()) { P(F("This test suite cannot be run as the root user.\n" "Please try again with a normal user account.\n")); @@ -797,94 +524,6 @@ LUAEXT(require_not_root, ) return 0; } -// Subroutine of run_tests_in_children. Spawn a child to run the single -// test TESTNAME. On Unix, can just fork and call back to Lua in the child. -// On Windows, must re-exec this process. -pid_t -run_one_test_in_child(lua_State * st, string const & testname, - string const & testdir) -{ -#ifdef WIN32 - // The bulk of the work is done in main(), -r case, and we don't have any - // grief about stdio. - 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(NULL_DEVICE, - "tester.log", - "tester.log", - argv); - change_current_working_dir(run_dir); - return child; - -#else - // 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. - - int retcode; - try - { - Lua ll(st); - ll.func("run_one_test"); - ll.push_str(testname); - ll.call(1,1) - .extract_int(retcode); - } - 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); - -#endif -} - // run_tests_in_children (to_run, reporter) // Run all of the tests in TO_RUN, each in its own isolated directory and @@ -918,7 +557,8 @@ LUAEXT(run_tests_in_children, ) do_remove_recursive(testdir); do_mkdir(testdir); - pid_t child = run_one_test_in_child(L, testname, testdir); + pid_t child = run_one_test_in_child(testname, testdir, + L, argv0, testfile, firstdir); if (child == -1) status = 127; // spawn failure