diff --git a/build/premake/premake.lua b/build/premake/premake.lua index 32ee38f348..327398917b 100755 --- a/build/premake/premake.lua +++ b/build/premake/premake.lua @@ -334,7 +334,9 @@ function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, e package_add_contents(source_root, rel_source_dirs, {}, extra_params) package_add_extern_libs(extern_libs) - tinsert(static_lib_names, package_name) + if not extra_params["no_default_link"] then + tinsert(static_lib_names, package_name) + end if OS == "windows" then tinsert(package.buildflags, "no-rtti") @@ -353,8 +355,10 @@ function setup_static_lib_package (package_name, rel_source_dirs, extern_libs, e -- correctly open these files from the IDE. -- * precompiled.cpp (needed to "Create" the PCH) also goes in -- the abovementioned dir. - pch_dir = source_root.."pch/"..package_name.."/" - package_setup_pch(pch_dir, "precompiled.h", "precompiled.cpp") + if not extra_params["no_default_pch"] then + pch_dir = source_root.."pch/"..package_name.."/" + package_setup_pch(pch_dir, "precompiled.h", "precompiled.cpp") + end end -- this is where the source tree is chopped up into static libs. @@ -486,7 +490,8 @@ function setup_all_libs () "vorbis", "libjpg", "cryptopp", - "valgrind" + "valgrind", + "cxxtest", } -- CPU architecture-specific @@ -518,6 +523,18 @@ function setup_all_libs () end setup_static_lib_package("lowlevel", source_dirs, extern_libs, {}) + + + -- CxxTest mock function support + extern_libs = { + "cxxtest", + } + -- 'real' implementations, to be linked against the main executable + setup_static_lib_package("mocks_real", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 }) + listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_real.cpp")) + -- 'test' implementations, to be linked against the test executable + setup_static_lib_package("mocks_test", {}, extern_libs, { no_default_link = 1, no_default_pch = 1 }) + listconcat(package.files, matchfiles(source_root.."mocks/*.h", source_root.."mocks/*_test.cpp")) end @@ -563,6 +580,7 @@ function setup_main_exe () package_add_extern_libs(used_extern_libs) + tinsert(package.links, "mocks_real") -- Platform Specifics if OS == "windows" then @@ -955,9 +973,9 @@ function setup_tests() package.testoptions = "--have-std" package.rootoptions = "--have-std" if OS == "windows" then - package.rootoptions = package.rootoptions .. " --gui=Win32Gui --runner=Win32ODSPrinter" + package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=Win32ODSPrinter" else - package.rootoptions = package.rootoptions .. " --runner=ErrorPrinter" + package.rootoptions = package.rootoptions .. " --gui=PsTestWrapper --runner=ErrorPrinter" end -- precompiled headers - the header is added to all generated .cpp files -- note that the header isn't actually precompiled here, only #included @@ -981,6 +999,7 @@ function setup_tests() -- note: these are not relative to source_root and therefore can't be included via package_add_contents. listconcat(package.files, src_files) package_add_extern_libs(used_extern_libs) + tinsert(package.links, "mocks_test") if OS == "windows" then -- from "lowlevel" static lib; must be added here to be linked in diff --git a/source/lib/sysdep/os/linux/linux.cpp b/source/lib/sysdep/os/linux/linux.cpp index 7628d73cf7..da44a65681 100644 --- a/source/lib/sysdep/os/linux/linux.cpp +++ b/source/lib/sysdep/os/linux/linux.cpp @@ -18,22 +18,124 @@ #include "precompiled.h" #include "lib/sysdep/sysdep.h" +#include "lib/external_libraries/boost_filesystem.h" #define GNU_SOURCE -#include +#include "mocks/dlfcn.h" +#include "mocks/boost_filesystem.h" + +// TODO: This normalization code is copied from boost::filesystem, +// where it is deprecated, presumably for good reasons (probably +// because symlinks mean "x/../y" != "y" in general), and this file +// is not an appropriate place for the code anyway, so it should be +// rewritten or removed or something. (Also, the const_cast is evil.) +namespace boost { namespace filesystem { + +// Derived from boost/filesystem/path.hpp: +// "Copyright Beman Dawes 2002-2005 +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)" + + template + basic_path normalize_COPIED(const basic_path &self) + { + typedef basic_path path_type; + typedef typename path_type::string_type string_type; + typedef typename path_type::iterator iterator; + + static const typename string_type::value_type dot_str[] + = { dot::value, 0 }; + + if ( self.empty() ) return self; + + path temp; + iterator start( self.begin() ); + iterator last( self.end() ); + iterator stop( last-- ); + for ( iterator itr( start ); itr != stop; ++itr ) + { + // ignore "." except at start and last + if ( itr->size() == 1 + && (*itr)[0] == dot::value + && itr != start + && itr != last ) continue; + + // ignore a name and following ".." + if ( !temp.empty() + && itr->size() == 2 + && (*itr)[0] == dot::value + && (*itr)[1] == dot::value ) // dot dot + { + string_type lf( temp.leaf() ); + if ( lf.size() > 0 + && (lf.size() != 1 + || (lf[0] != dot::value + && lf[0] != slash::value)) + && (lf.size() != 2 + || (lf[0] != dot::value + && lf[1] != dot::value +# ifdef BOOST_WINDOWS_PATH + && lf[1] != colon::value +# endif + ) + ) + ) + { + temp.remove_leaf(); + // if not root directory, must also remove "/" if any + if ( temp.string().size() > 0 + && temp.string()[temp.string().size()-1] + == slash::value ) + { + typename string_type::size_type rds( + detail::root_directory_start( temp.string(), + temp.string().size() ) ); + if ( rds == string_type::npos + || rds != temp.string().size()-1 ) + { const_cast( temp.string() ).erase( temp.string().size()-1 ); } + } + + iterator next( itr ); + if ( temp.empty() && ++next != stop + && next == last && *last == dot_str ) temp /= dot_str; + continue; + } + } + + temp /= *itr; + }; + + if ( temp.empty() ) temp /= dot_str; + return temp; + } + +} } LibError sys_get_executable_name(char* n_path, size_t max_chars) { + const char* path; Dl_info dl_info; + // Find the executable's filename memset(&dl_info, 0, sizeof(dl_info)); - if (!dladdr((void *)sys_get_executable_name, &dl_info) || + if (!T::dladdr((void *)sys_get_executable_name, &dl_info) || !dl_info.dli_fname ) { return ERR::NO_SYS; } + path = dl_info.dli_fname; - strncpy(n_path, dl_info.dli_fname, max_chars); - return INFO::OK; + // If this looks like a relative path, resolve against cwd. + // If this looks like an absolute path, we still need to normalize it. + if (strchr(path, '/')) { + fs::path p = fs::complete(fs::path(path), T::Boost_Filesystem_initial_path()); + fs::path n = fs::normalize_COPIED(p); + strncpy(n_path, n.string().c_str(), max_chars); + return INFO::OK; + } + + // If it's not a path at all, i.e. it's just a filename, we'd + // probably have to search through PATH to find it. + // That's complex and should be uncommon, so don't bother. + return ERR::NO_SYS; } - diff --git a/source/lib/sysdep/tests/test_sysdep.h b/source/lib/sysdep/tests/test_sysdep.h index 2970d19d9a..ea3f473296 100644 --- a/source/lib/sysdep/tests/test_sysdep.h +++ b/source/lib/sysdep/tests/test_sysdep.h @@ -17,9 +17,17 @@ #include "lib/self_test.h" +#include "lib/lib.h" #include "lib/sysdep/sysdep.h" #include "lib/posix/posix.h" // fminf etc. +#if OS_LINUX +# include "mocks/dlfcn.h" +# include "mocks/boost_filesystem.h" +#endif + +#include + class TestSysdep : public CxxTest::TestSuite { public: @@ -68,4 +76,106 @@ public: TS_ASSERT_EQUALS(fmaxf(-2.0f, 1.0f), 1.0f); TS_ASSERT_EQUALS(fmaxf(0.001f, 0.00001f), 0.001f); } + + void test_sys_get_executable_name() + { + char path[PATH_MAX] = ""; + + // Try it first with the real executable (i.e. the + // one that's running this test code) + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + // Check it's absolute + TSM_ASSERT(std::string("Path: ")+path, path_is_absolute(path)); + // Check the file exists + struct stat s; + TSM_ASSERT_EQUALS(std::string("Path: ")+path, stat(path, &s), 0); + + // Do some platform-specific tests, based on the + // implementations of sys_get_executable_name: + +#if OS_LINUX + // Try with absolute paths + { + Mock_dladdr d("/example/executable"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + TS_ASSERT_STR_EQUALS(path, "/example/executable"); + } + { + Mock_dladdr d("/example/./a/b/../c/../../executable"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + TS_ASSERT_STR_EQUALS(path, "/example/executable"); + } + + // Try with relative paths + { + Mock_dladdr d("./executable"); + Mock_initial_path m("/example"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + TS_ASSERT_STR_EQUALS(path, "/example/executable"); + } + { + Mock_dladdr d("./executable"); + Mock_initial_path m("/example/"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + TS_ASSERT_STR_EQUALS(path, "/example/executable"); + } + { + Mock_dladdr d("../d/../../e/executable"); + Mock_initial_path m("/example/a/b/c"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), INFO::OK); + TS_ASSERT_STR_EQUALS(path, "/example/a/e/executable"); + } + + // Try with pathless names + { + Mock_dladdr d("executable"); + TS_ASSERT_EQUALS(sys_get_executable_name(path, PATH_MAX), ERR::NO_SYS); + } +#endif // OS_LINUX + } + + // Mock classes for test_sys_get_executable_name +#if OS_LINUX + class Mock_dladdr : public T::Base_dladdr + { + public: + Mock_dladdr(const char* fname) : fname_(fname) { } + int dladdr(void *UNUSED(addr), Dl_info *info) { + info->dli_fname = fname_; + return 1; + } + private: + const char* fname_; + }; + + class Mock_initial_path : public T::Base_Boost_Filesystem_initial_path + { + public: + Mock_initial_path(const char* buf) : buf_(buf) { } + fs::path Boost_Filesystem_initial_path() { + return fs::path(buf_); + } + private: + const char* buf_; + }; +#endif + +private: + bool path_is_absolute(const char* path) + { + // UNIX-style absolute paths + if (path[0] == '/') + return true; + + // Windows UNC absolute paths + if (path[0] == '\\' && path[1] == '\\') + return true; + + // Windows drive-letter absolute paths + if (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) + return true; + + return false; + } + }; diff --git a/source/mocks/boost_filesystem.h b/source/mocks/boost_filesystem.h new file mode 100644 index 0000000000..770e26be0e --- /dev/null +++ b/source/mocks/boost_filesystem.h @@ -0,0 +1,10 @@ +#include "lib/external_libraries/boost_filesystem.h" +#include +CXXTEST_MOCK( + Boost_Filesystem_initial_path, + boost::filesystem::path, + Boost_Filesystem_initial_path, + (), + boost::filesystem::initial_path, + () +); diff --git a/source/mocks/dlfcn.h b/source/mocks/dlfcn.h new file mode 100644 index 0000000000..8dd7dab96c --- /dev/null +++ b/source/mocks/dlfcn.h @@ -0,0 +1,7 @@ +#include +#include +CXXTEST_MOCK_GLOBAL( + int, dladdr, + (void *addr, Dl_info *info), + (addr, info) +); diff --git a/source/mocks/mocks_real.cpp b/source/mocks/mocks_real.cpp new file mode 100644 index 0000000000..61b0f8d835 --- /dev/null +++ b/source/mocks/mocks_real.cpp @@ -0,0 +1,8 @@ +#define CXXTEST_MOCK_REAL_SOURCE_FILE +#include "lib/sysdep/os.h" + +#include "mocks/boost_filesystem.h" + +#if OS_LINUX +#include "mocks/dlfcn.h" +#endif // OS_LINUX diff --git a/source/mocks/mocks_test.cpp b/source/mocks/mocks_test.cpp new file mode 100644 index 0000000000..bf08c075b9 --- /dev/null +++ b/source/mocks/mocks_test.cpp @@ -0,0 +1,15 @@ +#define CXXTEST_MOCK_TEST_SOURCE_FILE +#include "lib/sysdep/os.h" + +// Cause calls to be redirected to the real function by default +#define DEFAULT(name) static T::Real_##name real_##name + +#include "mocks/boost_filesystem.h" +DEFAULT(Boost_Filesystem_initial_path); + +#if OS_LINUX + +#include "mocks/dlfcn.h" +DEFAULT(dladdr); + +#endif // OS_LINUX