diff --git a/src/platform.cpp b/src/platform.cpp new file mode 100644 index 0000000000..d457f85c98 --- /dev/null +++ b/src/platform.cpp @@ -0,0 +1,947 @@ +/* ---------------------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ +/** \file platform.cpp + * This file provides abstractions for a variety of platform specific + * functionality in a namespace "platform". This is a companion to + * the "utils" namespace with convenience and utility functions. */ + +#include "platform.h" +#include "utils.h" + +#if HAVE_MPI +#include +#endif + +//////////////////////////////////////////////////////////////////////// +// include system headers and tweak system settings +#if defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#if defined(_WIN32_WINNT) +#undef _WIN32_WINNT +#endif + +// target Windows version is windows 7 and later +#define _WIN32_WINNT _WIN32_WINNT_WIN7 +#define PSAPI_VERSION 2 + +#include +#include // for _get_osfhandle() +#include +#include + +#else // not Windows /////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(__APPLE__) +#include +#include +#endif +//////////////////////////////////////////////////////////////////////// + +#include + +/* ------------------------------------------------------------------ */ + +/// Struct for listing on-the-fly compression/decompression commands +struct zip_info { + /// identifier for the different compression algorithms + enum styles { NONE, GZIP, BZIP2, ZSTD, XZ, LZMA, LZ4 }; + const std::string extension; ///< filename extension for the current algorithm + const std::string command; ///< command to perform compression or decompression + const std::string zipflags; ///< flags to append to compress from stdin to stdout + const std::string unzipflags; ///< flags to decompress file to stdout + const int style; ///< compression style flag +}; + +// clang-format off +static const std::vector zip_styles = { + {"", "", "", "", zip_info::NONE}, + {"gz", "gzip", " > ", " -cdf ", zip_info::GZIP}, + {"bz2", "bzip2", " > ", " -cdf ", zip_info::BZIP2}, + {"zstd", "zstd", " -q > ", " -cdf ", zip_info::ZSTD}, + {"xz", "xz", " > ", " -cdf ", zip_info::XZ}, + {"lzma", "xz", " --format=lzma > ", " --format=lzma -cdf ", zip_info::LZMA}, + {"lz4", "lz4", " > ", " -cdf ", zip_info::LZ4}, +}; +// clang-format on + +/* ------------------------------------------------------------------ */ + +static const zip_info &find_zip_type(const std::string &file) +{ + std::size_t dot = file.find_last_of('.'); + if (dot != std::string::npos) { + const std::string ext = file.substr(dot + 1); + for (const auto &i : zip_styles) { + if (i.extension == ext) return i; + } + } + return zip_styles[0]; +} + +/* ------------------------------------------------------------------ */ + +using namespace LAMMPS_NS; + +// get CPU time + +// clang-format off +// clang compilers are optimizing this function too aggressively returning always 0 +#if defined(__clang__) +[[clang::optnone]] +#endif +double platform::cputime() +// clang-format on +{ + double rv = 0.0; + +#ifdef _WIN32 + + // from MSD docs. + FILETIME ct, et, kt, ut; + union { + FILETIME ft; + uint64_t ui; + } cpu; + if (GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + cpu.ft = ut; + rv = cpu.ui * 0.0000001; + } + +#else /* ! _WIN32 */ + + struct rusage ru; + if (getrusage(RUSAGE_SELF, &ru) == 0) { + rv = (double) ru.ru_utime.tv_sec; + rv += (double) ru.ru_utime.tv_usec * 0.000001; + } + +#endif + + return rv; +} + +/* ---------------------------------------------------------------------- + get wall time +------------------------------------------------------------------------ */ +double platform::walltime() +{ + double wtime; + +#if defined(_WIN32) + + wtime = GetTickCount64() * 0.001; + +#else + + struct timeval tv; + + gettimeofday(&tv, nullptr); + wtime = 1.0 * tv.tv_sec + 1.0e-6 * tv.tv_usec; + +#endif + + return wtime; +} + +/* ---------------------------------------------------------------------- + get Operating system and version info +------------------------------------------------------------------------- */ + +std::string platform::os_info() +{ + std::string buf; + +#if defined(_WIN32) + + // Get Windows Edition name from registry + char value[1024]; + DWORD value_length = 1024; + const char *subkey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + const char *entry = "ProductName"; + RegGetValue(HKEY_LOCAL_MACHINE, subkey, entry, RRF_RT_REG_SZ, nullptr, &value, + (LPDWORD) &value_length); + // enforce zero termination + value[1023] = '\0'; + buf = value; + + DWORD fullversion, majorv, minorv, buildv = 0; + fullversion = GetVersion(); + majorv = (DWORD) (LOBYTE(LOWORD(fullversion))); + minorv = (DWORD) (HIBYTE(LOWORD(fullversion))); + if (fullversion < 0x80000000) buildv = (DWORD) (HIWORD(fullversion)); + + buf += ", Windows ABI " + std::to_string(majorv) + "." + std::to_string(minorv) + " (" + + std::to_string(buildv) + ") on "; + + SYSTEM_INFO si; + GetSystemInfo(&si); + + switch (si.wProcessorArchitecture) { + case PROCESSOR_ARCHITECTURE_AMD64: + buf += "x86_64"; + break; + case PROCESSOR_ARCHITECTURE_ARM: + buf += "arm"; + break; + case PROCESSOR_ARCHITECTURE_IA64: + buf += "ia64"; + break; + case PROCESSOR_ARCHITECTURE_INTEL: + buf += "i386"; + break; + default: + buf += "(unknown)"; + } +#else + struct utsname ut; + uname(&ut); + + // try to get OS distribution name, if available + buf = ut.sysname; + +#if 0 // disable until this is integrated into LAMMPS and TextFileReader becomes available + if (utils::file_is_readable("/etc/os-release")) { + try { + TextFileReader reader("/etc/os-release",""); + while (1) { + auto words = reader.next_values(0,"="); + if ((words.count() > 1) && (words.next_string() == "PRETTY_NAME")) { + distro += " " + utils::trim(words.next_string()); + break; + } + } + } catch (std::exception &e) { + ; // EOF but keyword not found + } + } +#endif + + buf += std::string(" ") + ut.release + " " + ut.machine; +#endif + return buf; +} + +/* ---------------------------------------------------------------------- + identify C++ standard version +------------------------------------------------------------------------- */ + +std::string platform::cxx_standard() +{ +#if __cplusplus > 202002L + return "newer than C++20"; +#elif __cplusplus == 202002L + return "C++20"; +#elif __cplusplus == 201703L + return "C++17"; +#elif __cplusplus == 201402L + return "C++14"; +#elif __cplusplus == 201103L + return "C++11"; +#elif __cplusplus == 199711L + return "C++98"; +#else + return "unknown"; +#endif +} + +/* ---------------------------------------------------------------------- + identify compiler and its version +------------------------------------------------------------------------- */ + +std::string platform::compiler_info() +{ + std::string buf = "(Unknown)"; +#if 0 // disable for now untile merged into LAMMPS and fmt:: becomes available +#if defined(__INTEL_LLVM_COMPILER) + double version = static_cast(__INTEL_LLVM_COMPILER)*0.01; + buf = fmt::format("Intel LLVM C++ {:.1f} / {}", version, __VERSION__); +#elif defined(__ibmxl__) + buf = fmt::format("IBM XL C/C++ (Clang) {}.{}.{}", + __ibmxl_version__, __ibmxl_release__, __ibmxl_modification__); +#elif defined(__clang__) + buf = fmt::format("Clang C++ {}", __VERSION__); +#elif defined(__PGI) + buf = fmt::format("PGI C++ {}.{}",__PGIC__,__PGIC_MINOR__); +#elif defined(__INTEL_COMPILER) + double version = static_cast(__INTEL_COMPILER)*0.01; + buf = fmt::format("Intel Classic C++ {:.2f}.{} / {}", version, + __INTEL_COMPILER_UPDATE, __VERSION__); +#elif defined(__MINGW64__) + buf = fmt::format("MinGW-w64 64bit {}.{} / GNU C++ {}", __MINGW64_VERSION_MAJOR, + __MINGW64_VERSION_MINOR, __VERSION__); +#elif defined(__MINGW32__) + buf = fmt::format("MinGW-w64 32bit {}.{} / GNU C++ {}", __MINGW32_MAJOR_VERSION, + __MINGW32_MINOR_VERSION, __VERSION__); +#elif defined(__GNUC__) + buf = fmt::format("GNU C++ {}", __VERSION__); +#elif defined(_MSC_VER) && (_MSC_VER > 1920) && (_MSC_VER < 2000) + constexpr int major = _MSC_VER / 100; + constexpr int minor = _MSC_VER - major *100; + buf = "Microsoft Visual Studio 20" + std::to_string(major) + ", C/C++ " + std::to_string(major-5) + "." + std::to_string(minor); +#else + buf = "(Unknown)"; +#endif +#endif + return buf; +} + +/* ---------------------------------------------------------------------- + detect OpenMP standard +------------------------------------------------------------------------- */ + +std::string platform::openmp_standard() +{ + +#if !defined(_OPENMP) + return "OpenMP not enabled"; +#else + + // Supported OpenMP version corresponds to the release date of the + // specifications as posted at https://www.openmp.org/specifications/ + +#if _OPENMP > 202011 + return "OpenMP newer than version 5.1"; +#elif _OPENMP == 202011 + return "OpenMP 5.1"; +#elif _OPENMP == 201811 + return "OpenMP 5.0"; +#elif _OPENMP == 201611 + return "OpenMP 5.0 preview 1"; +#elif _OPENMP == 201511 + return "OpenMP 4.5"; +#elif _OPENMP == 201307 + return "OpenMP 4.0"; +#elif _OPENMP == 201107 + return "OpenMP 3.1"; +#elif _OPENMP == 200805 + return "OpenMP 3.0"; +#elif _OPENMP == 200505 + return "OpenMP 2.5"; +#elif _OPENMP == 200203 + return "OpenMP 2.0"; +#else + return "unknown OpenMP version"; +#endif + +#endif +} + +/* ---------------------------------------------------------------------- + identify MPI vendor from defines in the mpi.h file. +------------------------------------------------------------------------- */ + +std::string platform::mpi_vendor() +{ +#if defined(MPI_STUBS) + return "MPI STUBS"; +#elif defined(OPEN_MPI) + return "Open MPI"; +#elif defined(MPICH_NAME) + return "MPICH"; +#elif defined(I_MPI_VERSION) + return "Intel MPI"; +#elif defined(PLATFORM_MPI) + return "Platform MPI"; +#elif defined(HP_MPI) + return "HP MPI"; +#elif defined(MSMPI_VER) + // Get Microsoft MPI version from registry + char value[1024]; + DWORD value_length = 1024; + const char *subkey = "SOFTWARE\\Microsoft\\MPI"; + const char *entry = "Version"; + auto rv = RegGetValueA(HKEY_LOCAL_MACHINE, subkey, entry, RRF_RT_REG_SZ, nullptr, &value, + (LPDWORD) &value_length); + std::string buf = "Microsoft MPI"; + if (rv == ERROR_SUCCESS) buf += std::string(" v") + value; + return buf; +#else + return "Unknown MPI implementation"; +#endif +} + +/* ---------------------------------------------------------------------- + detect MPI version info +------------------------------------------------------------------------- */ + +std::string platform::mpi_info(int &major, int &minor) +{ + int len = 0; +#if (defined(MPI_VERSION) && (MPI_VERSION > 2)) || defined(MPI_STUBS) + static char version[MPI_MAX_LIBRARY_VERSION_STRING]; + MPI_Get_library_version(version, &len); +#else + constexpr int MAX_VERSION_STRING = 32; + static char version[MAX_VERSION_STRING]; + strncpy(version, mpi_vendor().c_str(), MAX_VERSION_STRING); +#endif + +#if defined(MPI_VERSION) + MPI_Get_version(&major, &minor); + if (len > 80) { + char *ptr = strchr(version + 80, '\n'); + if (ptr) *ptr = '\0'; + } +#else + major = 1; + minor = 0; +#endif + return std::string(version); +} + +/* ---------------------------------------------------------------------- + set environment variable +------------------------------------------------------------------------- */ + +int platform::putenv(const std::string &vardef) +{ + if (vardef.size() == 0) return -1; + + auto found = vardef.find_first_of('='); +#ifdef _WIN32 + // must assign a value to variable with _putenv() + if (found == std::string::npos) + return _putenv(utils::strdup(vardef + "=1")); + else + return _putenv(utils::strdup(vardef)); +#else + if (found == std::string::npos) + return setenv(vardef.c_str(), "", 1); + else + return setenv(vardef.substr(0, found).c_str(), vardef.substr(found + 1).c_str(), 1); +#endif + return -1; +} + +/* ---------------------------------------------------------------------- + split a "path" environment variable into a list +------------------------------------------------------------------------- */ + +std::vector platform::list_pathenv(const std::string &var) +{ + std::vector dirs; + const char *ptr = getenv(var.c_str()); + if (ptr == nullptr) return dirs; + + std::string pathvar = ptr; + std::size_t first = 0, next; + while (true) { + next = pathvar.find_first_of(pathvarsep, first); + if (next == std::string::npos) { + dirs.push_back(pathvar.substr(first)); + break; + } else { + dirs.push_back(pathvar.substr(first, next - first)); + first = next + 1; + } + } + return dirs; +} + +/* ---------------------------------------------------------------------- + find the full path name of an executable +------------------------------------------------------------------------- */ + +std::string platform::find_exe_path(const std::string &cmd) +{ + if (cmd.size() == 0) return ""; + auto pathdirs = list_pathenv("PATH"); +#ifdef _WIN32 + // windows always looks in "." and does it first + pathdirs.insert(pathdirs.begin(), "."); +#else + struct stat info; +#endif + for (const auto &dir : pathdirs) { + std::string exe = path_join(dir, cmd); +#ifdef _WIN32 + const char *extensions[] = {".exe", ".com", ".bat", nullptr}; + for (auto ext = extensions; *ext != nullptr; ++ext) { + auto exe_path = exe + *ext; + if (file_is_readable(exe_path)) return exe_path; + } +#else + memset(&info, 0, sizeof(info)); + if (stat(exe.c_str(), &info) != 0) continue; + if ((info.st_mode & (S_IXOTH | S_IXGRP | S_IXUSR)) != 0) return exe; +#endif + } + return ""; +} + +/* ---------------------------------------------------------------------- + wrapper functions for loading shared objects and libraries +------------------------------------------------------------------------- */ + +#ifdef _WIN32 + +// open a shared object file +void *platform::dlopen(const std::string &fname) +{ + return (void *) LoadLibrary(fname.c_str()); +} + +// close a shared object +int platform::dlclose(void *handle) +{ + /* FreeLibrary returns nonzero on success unlike dlclose() */ + return (FreeLibrary((HINSTANCE) handle) == 0); +} + +// resolve a symbol in shared object +void *platform::dlsym(void *handle, const std::string &symbol) +{ + return (void *) GetProcAddress((HINSTANCE) handle, symbol.c_str()); +} + +#else + +// open a shared object file +void *platform::dlopen(const std::string &fname) +{ + return ::dlopen(fname.c_str(), RTLD_NOW); +} + +// close a shared object +int platform::dlclose(void *handle) +{ + return ::dlclose(handle); +} + +// resolve a symbol in shared object +void *platform::dlsym(void *handle, const std::string &symbol) +{ + return ::dlsym(handle, symbol.c_str()); +} +#endif + +/* ---------------------------------------------------------------------- */ + +/** On Linux the folder /proc/self/fd holds symbolic links to the actual + * pathnames associated with each open file descriptor of the current process. + * On macOS the same kind of information can be obtained using ``fcntl(fd,F_GETPATH,buf)``. + * On Windows we use ``GetFinalPathNameByHandleA()`` which is available with + * Windows Vista and later. If the buffer is to small (< 16 bytes) a null pointer is returned. + * + * This function is used to provide a filename with error messages in functions + * where the filename is not passed as an argument, but the FILE * pointer. */ + +const char *platform::guesspath(FILE *fp, char *buf, int len) +{ + // no point in guessing a path with a short buffer or NULL pointer as buffer + if ((buf == nullptr) || (len < 16)) return nullptr; + + // zero buffer and reserve last character in buffer for terminating '\0' + memset(buf, 0, len); + len--; + +#if defined(__linux__) + + int fd = fileno(fp); + // get pathname from /proc or copy (unknown) + if (readlink((std::string("/proc/self/fd/") + std::to_string(fd)).c_str(), buf, len) <= 0) + strncpy(buf, "(unknown)", len); + +#elif defined(__APPLE__) + + int fd = fileno(fp); + char filepath[PATH_MAX]; + if (fcntl(fd, F_GETPATH, filepath) != -1) + strncpy(buf, filepath, len); + else + strncpy(buf, "(unknown)", len); + +#elif defined(_WIN32) + + char filepath[MAX_PATH]; + HANDLE h = (HANDLE) _get_osfhandle(_fileno(fp)); + if (GetFinalPathNameByHandleA(h, filepath, MAX_PATH, FILE_NAME_NORMALIZED) > 0) + strncpy(buf, filepath, len); + else + strncpy(buf, "(unknown)", len); + +#else // unsupported OS + + strncpy(buf, "(unknown)", len); + +#endif + + return buf; +} + +/* ---------------------------------------------------------------------- + detect terminal, e.g. for using a pager automatically +------------------------------------------------------------------------- */ + +bool platform::is_console(FILE *fp) +{ + if (!fp) return false; +#if defined(_WIN32) + return (_isatty(fileno(fp)) == 1); +#else + return (isatty(fileno(fp)) == 1); +#endif +} + +/* ---------------------------------------------------------------------- + Get string with path to the current directory + PATH_MAX may not be a compile time constant, so we must allocate and delete a buffer. +------------------------------------------------------------------------- */ + +std::string platform::current_directory() +{ + std::string cwd = ""; + +#if defined(_WIN32) + char *buf = new char[MAX_PATH]; + if (_getcwd(buf, MAX_PATH)) { cwd = buf; } + delete[] buf; +#else + char *buf = new char[PATH_MAX]; + if (::getcwd(buf, PATH_MAX)) { cwd = buf; } + delete[] buf; +#endif + return cwd; +} + +/* ---------------------------------------------------------------------- + check if a path is a directory +------------------------------------------------------------------------- */ + +bool platform::path_is_directory(const std::string &path) +{ +#if defined(_WIN32) + struct _stat info; + memset(&info, 0, sizeof(info)); + if (_stat(path.c_str(), &info) != 0) return false; +#else + struct stat info; + memset(&info, 0, sizeof(info)); + if (stat(path.c_str(), &info) != 0) return false; +#endif + return ((info.st_mode & S_IFDIR) != 0); +} + +/* ---------------------------------------------------------------------- + get directory listing in string vector +------------------------------------------------------------------------- */ + +std::vector platform::list_directory(const std::string &dir) +{ + std::vector files; + if (!path_is_directory(dir)) return files; + +#if defined(_WIN32) + HANDLE handle; + WIN32_FIND_DATA fd; + std::string searchname = dir + filepathsep[0] + "*"; + handle = FindFirstFile(searchname.c_str(), &fd); + if (handle == ((HANDLE) -1)) return files; + while (FindNextFile(handle, &fd)) { + std::string entry(fd.cFileName); + if ((entry == "..") || (entry == ".")) continue; + files.push_back(entry); + } + FindClose(handle); +#else + std::string dirname = dir + filepathsep[0]; + DIR *handle = opendir(dirname.c_str()); + if (handle == nullptr) return files; + struct dirent *fd; + while ((fd = readdir(handle)) != nullptr) { + std::string entry(fd->d_name); + if ((entry == "..") || (entry == ".")) continue; + files.push_back(entry); + } + closedir(handle); +#endif + return files; +} + +/* ---------------------------------------------------------------------- + Change current directory +------------------------------------------------------------------------- */ + +int platform::chdir(const std::string &path) +{ +#if defined(_WIN32) + return ::_chdir(path.c_str()); +#else + return ::chdir(path.c_str()); +#endif +} + +/* ---------------------------------------------------------------------- + Create a directory +------------------------------------------------------------------------- */ + +int platform::mkdir(const std::string &path) +{ +#if defined(_WIN32) + return ::_mkdir(path.c_str()); +#else + return ::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP); +#endif +} + +/* ---------------------------------------------------------------------- + Delete a directory and its contents recursively +------------------------------------------------------------------------- */ + +int platform::rmdir(const std::string &path) +{ + // recurse through directory tree deleting files and directories + auto entries = list_directory(path); + for (const auto &entry : entries) { + const auto newpath = path_join(path, entry); + if (path_is_directory(newpath)) + rmdir(newpath); + else + unlink(newpath); + } +#if defined(_WIN32) + return ::_rmdir(path.c_str()); +#else + return ::rmdir(path.c_str()); +#endif +} + +/* ---------------------------------------------------------------------- + Delete a file +------------------------------------------------------------------------- */ + +int platform::unlink(const std::string &path) +{ +#if defined(_WIN32) + return ::_unlink(path.c_str()); +#else + return ::unlink(path.c_str()); +#endif +} + +/* ---------------------------------------------------------------------- + Get current file stream position +------------------------------------------------------------------------- */ + +bigint platform::ftell(FILE *fp) +{ +#if defined(_WIN32) + return (bigint)::_ftelli64(fp); +#else + return (bigint)::ftell(fp); +#endif +} + +/* ---------------------------------------------------------------------- + Set current file stream position +------------------------------------------------------------------------- */ + +int platform::fseek(FILE *fp, bigint pos) +{ +#if defined(_WIN32) + return ::_fseeki64(fp, (__int64) pos, SEEK_SET); +#else + return ::fseek(fp, (long) pos, SEEK_SET); +#endif +} + +/* ---------------------------------------------------------------------- + Truncate opened file to given length +------------------------------------------------------------------------- */ + +int platform::ftruncate(FILE *fp, bigint length) +{ +#if defined(_WIN32) + HANDLE h = (HANDLE) _get_osfhandle(_fileno(fp)); + LARGE_INTEGER li_start, li_length; + li_start.QuadPart = (int64_t) 0; + li_length.QuadPart = (int64_t) length; + if (SetFilePointerEx(h, li_start, NULL, FILE_CURRENT) && + SetFilePointerEx(h, li_length, NULL, FILE_BEGIN) && SetEndOfFile(h)) { + return 0; + } else { + return 1; + } +#else + platform::fseek(fp, length); + return ::ftruncate(fileno(fp), (off_t) length); +#endif +} + +/* ---------------------------------------------------------------------- + open pipe +------------------------------------------------------------------------- */ + +FILE *platform::popen(const std::string &cmd, const std::string &mode) +{ + FILE *fp = nullptr; +#if defined(_WIN32) + if (mode == "r") + fp = ::_popen(cmd.c_str(), "rb"); + else if (mode == "w") + fp = ::_popen(cmd.c_str(), "wb"); +#else + if (mode == "r") + fp = ::popen(cmd.c_str(), "r"); + else if (mode == "w") + fp = ::popen(cmd.c_str(), "w"); +#endif + return fp; +} + +/* ---------------------------------------------------------------------- + close pipe +------------------------------------------------------------------------- */ + +int platform::pclose(FILE *fp) +{ +#if defined(_WIN32) + return ::_pclose(fp); +#else + return ::pclose(fp); +#endif +} + +/* ---------------------------------------------------------------------- + strip off leading part of path, return just the filename +------------------------------------------------------------------------- */ + +std::string platform::path_basename(const std::string &path) +{ + size_t start = path.find_last_of(platform::filepathsep); + + if (start == std::string::npos) { + start = 0; + } else { + start += 1; + } + + return path.substr(start); +} + +/* ---------------------------------------------------------------------- + Return only the leading part of a path, return just the directory +------------------------------------------------------------------------- */ + +std::string platform::path_dirname(const std::string &path) +{ + size_t start = path.find_last_of(platform::filepathsep); + + if (start == std::string::npos) return "."; + + return path.substr(0, start); +} + +/* ---------------------------------------------------------------------- + join two paths. + if one of the two is an empty string just return the other unmodified + if the first string ends in the separator or the second begins with one, trim them +------------------------------------------------------------------------- */ + +std::string platform::path_join(const std::string &a, const std::string &b) +{ + if (a.empty()) return b; + if (b.empty()) return a; + + // remove trailing separator(s) in first part + std::string joined = a; + while (joined.find_last_of(platform::filepathsep) == joined.size() - 1) { + for (const auto &s : platform::filepathsep) + if (joined.back() == s) joined.pop_back(); + } + + // skip over leading separator(s) in second part + std::size_t skip = 0; + while (b.find_first_of(platform::filepathsep, skip) == skip) ++skip; + + // combine and return + joined += platform::filepathsep[0] + b.substr(skip); + return joined; +} + +/* ---------------------------------------------------------------------- + try to open file for reading to prove if it exists and is accessible +------------------------------------------------------------------------- */ + +bool platform::file_is_readable(const std::string &path) +{ + FILE *fp = fopen(path.c_str(), "r"); + if (fp) { + fclose(fp); + return true; + } + return false; +} + +/* ---------------------------------------------------------------------- + check if filename has a known compression extension +------------------------------------------------------------------------- */ + +bool platform::has_zip_extension(const std::string &file) +{ + return find_zip_type(file).style != zip_info::NONE; +} + +/* ---------------------------------------------------------------------- + open pipe to read a compressed file +------------------------------------------------------------------------- */ + +FILE *platform::zip_read(const std::string &file) +{ + FILE *fp = nullptr; + +#if defined(LAMMPS_GZIP) + auto zip = find_zip_type(file); + if (zip.style == zip_info::NONE) return nullptr; + + if (find_exe_path(zip.command).size()) + // put quotes around file name so that they may contain blanks + fp = popen((zip.command + zip.unzipflags + "\"" + file + "\""), "r"); +#endif + return fp; +} + +/* ---------------------------------------------------------------------- + open pipe to write a compressed file +------------------------------------------------------------------------- */ + +FILE *platform::zip_write(const std::string &file) +{ + FILE *fp = nullptr; + +#if defined(LAMMPS_GZIP) + auto zip = find_zip_type(file); + if (zip.style == zip_info::NONE) return nullptr; + + if (find_exe_path(zip.command).size()) + // put quotes around file name so that they may contain blanks + fp = popen((zip.command + zip.zipflags + "\"" + file + "\""), "w"); +#endif + return fp; +} + +/* ---------------------------------------------------------------------- */ diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000000..5aeee1545e --- /dev/null +++ b/src/platform.h @@ -0,0 +1,350 @@ +/* -*- c++ -*- ---------------------------------------------------------- + LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator + https://www.lammps.org/, Sandia National Laboratories + Steve Plimpton, sjplimp@sandia.gov + + Copyright (2003) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level LAMMPS directory. +------------------------------------------------------------------------- */ + +#ifndef LMP_PLATFORM_H +#define LMP_PLATFORM_H + +/*! \file platform.h */ + +#include "lmptype.h" + +#include +#include + +namespace LAMMPS_NS { +namespace platform { + + /*! Return the consumed CPU time for the current process in seconds + * + * This is a wrapper about the POSIX function getrusage() and the Windows equivalent. + * It is to be used in a similar fashion than MPI_Wtime(). + * + * \return used CPU time in second */ + + double cputime(); + + /*! Return the wall clock state for the current process in seconds + * + * This is a wrapper about XXX and its Windows equivalent. + * It is to be used in a similar fashion than MPI_Wtime(). + * + * \return wall clock time in second */ + + double walltime(); + + /*! Return string with the Operating system version and architecture info. + * + * \return string with info about the OS and the platform is is running on */ + + std::string os_info(); + + /*! Return string with C++ standard version used to compile LAMMPS. + * + * This function uses predefined compiler macros to identify + * the C++ standard version used to compile LAMMPS with. + * + * \return string with the C++ standard version or "unknown" */ + + std::string cxx_standard(); + + /*! Return string with compiler version info + * + * This function uses predefined compiler macros to identify + * Compilers and their version and configuration info. + * + * \return string with the compiler information text */ + + std::string compiler_info(); + + /*! Return string with OpenMP standard version info + * + * This function uses predefined compiler macros to identify + * OpenMP support and the supported version of the standard. + * + * \return string with the openmp information text */ + + std::string openmp_standard(); + + /*! Return string with MPI vendor info + * + * This function uses predefined macros to identify + * the vendor of the MPI library used. + * + * \return string with the MPI vendor information text */ + + std::string mpi_vendor(); + + /*! Return string with MPI version info + * + * This function uses predefined macros and MPI function + * calls to identify the version of the MPI library used. + * + * \param major major version of the MPI standard (set on exit) + * \param minor minor version of the MPI standard (set on exit) + * \return string with the MPI version information text */ + + std::string mpi_info(int &major, int &minor); + + /*! Add variable to the environment + * + * \param vardef variable name or variable definition (NAME=value) + * \return -1 if failure otherwise 0 */ + + int putenv(const std::string &vardef); + + /*! Get list of entries in a path environment variable + * + * This provides a list of strings of the entries in an environment + * variable that is containing a "path" like "PATH" or "LD_LIBRARY_PATH". + * + * \param var name of the environment variable + * \return vector with strings of all entries in that path variable */ + + std::vector list_pathenv(const std::string &var); + + /*! Open a shared object file or library + * + * \param fname name or path of the shared object + * \return handle to the shared object or null */ + + void *dlopen(const std::string &fname); + + /*! Close a shared object + * This releases the object corresponding to the provided handle. + * Resolved symbols associated with this handle may not be used + * after this call + * + * \param handle handle to an opened shared object + * \return 0 if succesful, non-zero of not */ + + int dlclose(void *handle); + + /*! Resolve a symbol in shared object + * + * \param handle handle to an opened shared object + * \param symbol name of the symbol to extract + * \return pointer to the resolved symbol or null */ + + void *dlsym(void *handle, const std::string &symbol); + + /*! File path component separators + * These are the characters that separate directories and filename in paths on + * a platform. If multiple are provided, the first is the preferred one. */ + +#if defined(_WIN32) + constexpr char filepathsep[] = "\\/"; +#else + constexpr char filepathsep[] = "/"; +#endif + + /*! Path environment variable component separator + * This is the character that separates entries in "PATH" environment variables. */ + +#if defined(_WIN32) + constexpr char pathvarsep = ';'; +#else + constexpr char pathvarsep = ':'; +#endif + + /*! Try to detect pathname from FILE pointer. + * + * Currently only supported on Linux and macOS, otherwise will report "(unknown)". + * + * \param fp FILE pointer struct from STDIO library for which we want to detect the name + * \param buf storage buffer for pathname. output will be truncated if not large enough + * \param len size of storage buffer. output will be truncated to this length - 1 + * \return pointer to the storage buffer with path or a NULL pointer if buf is invalid + * or the buffer size is too small */ + + const char *guesspath(FILE *fp, char *buf, int len); + + /*! Check if a file pointer may be connected to a console + * + * \param fp file pointer + * \return true if the file pointer is flagged as a TTY */ + + bool is_console(FILE *fp); + + /*! Get string with path to the current directory + * + * \return path to the current directory or empty string */ + + std::string current_directory(); + + /*! Check if a path is a directory + * + * \param path directory path + * \return true if the directory exists */ + + bool path_is_directory(const std::string &path); + + /*! Get list of entries in a directory + * + * This provides a list of strings of the entries in the directory + * without the leading path name while also skipping over ".." and ".". + * + * \param path path to directory + * \return vector with strings of all directory entries */ + + std::vector list_directory(const std::string &dir); + + /*! Find pathname of an executable in the standard search path + * + * This function will traverse the list of directories in the PATH + * environment variable and look for the executable *cmd*. If the + * file exists and is executable the full path is returned as string, + * otherwise and emptry string is returned. + * + * On Windows the *cmd* string must not include and extension as + * this function will automatically append the extensions ".exe", + * ".com" and ".bat" and look for those paths. On Windows also the + * current directory is checked (and first), while otherwise not unless + * "." exists in the PATH environment variable. + * + * Because of the nature of the check, this will not detect shell functions + * built-in command or aliases. + * + * \param cmd name of command + * \return vector with strings of all directory entries */ + + std::string find_exe_path(const std::string &cmd); + + /*! Change current directory + * + * \param path new current working directory path + * \return -1 if unsuccessful, otherwise >= 0 */ + + int chdir(const std::string &path); + + /*! Create a directory + * + * \param path directory path + * \return -1 if unsuccessful, otherwise >= 0 */ + + int mkdir(const std::string &path); + + /*! Delete a directory + * + * \param path directory path + * \return -1 if unsuccessful, otherwise >= 0 */ + + int rmdir(const std::string &path); + + /*! Delete a directory and its contents + * + * Unlike the the ``rmdir()`` or ``_rmdir()`` function of the + * C library, this function will check for the contents of the + * folder and recurse into any sub-folders, if necessary and + * delete all contained folders and their contents before + * deleting the folder *path*. + * + * \param path path to file to be deleted + * \return 0 on success, -1 on error */ + + int unlink(const std::string &path); + + /*! Get current file position + * + * \param fp FILE pointer of the given file + * \return current FILE pointer position cast to a bigint */ + + bigint ftell(FILE *fp); + + /*! Set absolute file position + * + * \param fp FILE pointer of the given file + * \param pos new position of the FILE pointer + * \return 0 if successful, otherwise -1 */ + + int fseek(FILE *fp, bigint pos); + + /*! Truncate file to a given length and reposition file pointer + * + * \param fp FILE pointer of the given file + * \param length length to which the file is being truncated to + * \return 0 if successful, otherwise -1 */ + + int ftruncate(FILE *fp, bigint length); + + /*! Open a pipe to a command for reading or writing + * + * \param cmd command for the pipe + * \param mode "r" for reading from *cmd* or "w" for writing to *cmd* + * \return file pointer to the pipe if successful or null */ + + FILE *popen(const std::string &cmd, const std::string &mode); + + /*! Close a previously opened pipe + * + * \param fp FILE pointer for the pipe + * \return exit status of the pipe command or -1 in case of errors */ + + int pclose(FILE *fp); + + /*! Strip off leading part of path, return just the filename + * + * \param path file path + * \return file name */ + + std::string path_basename(const std::string &path); + + /*! Return the directory part of a path. Return "." if empty + * + * \param path file path + * \return directory name */ + + std::string path_dirname(const std::string &path); + + /*! Join two pathname segments + * + * This uses the forward slash '/' character unless LAMMPS is compiled + * for Windows where it used the equivalent backward slash '\\'. + * + * \param a first path + * \param b second path + * \return combined path */ + + std::string path_join(const std::string &a, const std::string &b); + + /*! Check if file exists and is readable + * + * \param path file path + * \return true if file exists and is readable */ + + bool file_is_readable(const std::string &path); + + /*! Check if a file name ends in a known extension for a compressed file format + * Currently supported extensions are: .Z, .gz, .bz2, .zstd, .xz, .lzma + * + * \param file name of the file to check + * \return true if the file has a known extension, otherwise false */ + + bool has_zip_extension(const std::string &file); + + /*! Open pipe to compressed text file for reading. + * + * \param file name of the file to open + * \return FILE pointer to pipe using for reading the compressed file. */ + + FILE *zip_read(const std::string &file); + + /*! Open pipe to compressed text file for writing. + * + * \param file name of the file to open + * \return FILE pointer to pipe using for reading the compressed file. */ + + FILE *zip_write(const std::string &file); + +} // namespace platform +} // namespace LAMMPS_NS +#endif diff --git a/unittest/utils/CMakeLists.txt b/unittest/utils/CMakeLists.txt index c1ce7c136f..d58b1294be 100644 --- a/unittest/utils/CMakeLists.txt +++ b/unittest/utils/CMakeLists.txt @@ -15,6 +15,22 @@ target_link_libraries(test_utils PRIVATE lammps GTest::GMockMain GTest::GMock GT add_test(Utils test_utils) set_tests_properties(Utils PROPERTIES ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") +add_executable(test_platform test_platform.cpp) +target_link_libraries(test_platform PRIVATE lammps GTest::GMockMain GTest::GMock GTest::GTest) +add_test(Platform test_platform) +set(PLATFORM_ENVIRONMENT "LAMMPS_POTENTIALS=${LAMMPS_POTENTIALS_DIR}") +if(BUILD_SHARED_LIBS) + enable_language(C) + target_compile_definitions(test_platform PRIVATE -DTEST_SHARED_OBJECT=1) + add_library(testsharedobj MODULE testshared.c) + set_target_properties(testsharedobj PROPERTIES PREFIX "" WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + add_library(testsharedlib SHARED testshared.c) + set_target_properties(testsharedlib PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE) + add_dependencies(test_platform testsharedobj testsharedlib) + list(APPEND PLATFORM_ENVIRONMENT "TEST_SHARED_LIB=$" "TEST_SHARED_OBJ=$") +endif() +set_tests_properties(Platform PROPERTIES ENVIRONMENT ${PLATFORM_ENVIRONMENT} WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + add_executable(test_fmtlib test_fmtlib.cpp) target_link_libraries(test_fmtlib PRIVATE lammps GTest::GMockMain GTest::GMock GTest::GTest) add_test(FmtLib test_fmtlib) diff --git a/unittest/utils/test_platform.cpp b/unittest/utils/test_platform.cpp new file mode 100644 index 0000000000..154fb2c222 --- /dev/null +++ b/unittest/utils/test_platform.cpp @@ -0,0 +1,395 @@ + +#include "platform.h" +#include "utils.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include + +using namespace LAMMPS_NS; +using testing::EndsWith; +using testing::Eq; +using testing::IsEmpty; +using testing::StartsWith; +using testing::StrEq; + +TEST(Platform, clock) +{ + const double wt_start = platform::walltime(); + const double ct_start = platform::cputime(); + + // spend some time computing pi + constexpr double known_pi = 3.141592653589793238462643; + constexpr int n = 10000000; + constexpr double h = 1.0 / (double) n; + double my_pi = 0.0, x; + for (int i = 0; i < n; ++i) { + x = h * ((double) i + 0.5); + my_pi += 4.0 / (1.0 + x * x); + } + my_pi *= h; + const double wt_used = platform::walltime() - wt_start; + const double ct_used = platform::cputime() - ct_start; + + ASSERT_NEAR(my_pi, known_pi, 1e-12); + ASSERT_GT(wt_used, 1e-4); + ASSERT_GT(ct_used, 1e-4); +} + +TEST(Platform, putenv) +{ + const char *var = getenv("UNITTEST_VAR1"); + ASSERT_EQ(var, nullptr); + int rv = platform::putenv("UNITTEST_VAR1"); + var = getenv("UNITTEST_VAR1"); + ASSERT_EQ(rv, 0); + ASSERT_NE(var, nullptr); + // we cannot set environment variables without a value on windows with _putenv() +#if defined(_WIN32) + ASSERT_THAT(var, StrEq("1")); +#else + ASSERT_THAT(var, StrEq("")); +#endif + + rv = platform::putenv("UNITTEST_VAR1=one"); + var = getenv("UNITTEST_VAR1"); + ASSERT_EQ(rv, 0); + ASSERT_NE(var, nullptr); + ASSERT_THAT(var, StrEq("one")); + + rv = platform::putenv("UNITTEST_VAR1=one=two"); + var = getenv("UNITTEST_VAR1"); + ASSERT_EQ(rv, 0); + ASSERT_NE(var, nullptr); + ASSERT_THAT(var, StrEq("one=two")); + + ASSERT_EQ(platform::putenv(""), -1); +} + +TEST(Platform, list_pathenv) +{ + auto dirs = platform::list_pathenv("PATH"); + ASSERT_GT(dirs.size(), 1); +} + +TEST(Platform, find_cmd_path) +{ +#if defined(_WIN32) + ASSERT_THAT(platform::find_exe_path("notepad"), EndsWith("\\notepad.exe")); + ASSERT_THAT(platform::find_exe_path("cmd"), EndsWith("\\cmd.exe")); + ASSERT_THAT(platform::find_exe_path("some_bogus_command"), IsEmpty()); +#else + ASSERT_THAT(platform::find_exe_path("ls"), EndsWith("bin/ls")); + ASSERT_THAT(platform::find_exe_path("sh"), EndsWith("bin/sh")); + ASSERT_THAT(platform::find_exe_path("some_bogus_command"), IsEmpty()); +#endif +} + +TEST(Platform, sharedload) +{ + const char *objs[] = {"TEST_SHARED_OBJ", "TEST_SHARED_LIB", nullptr}; + const char *envvar, **envptr; + const int *intvar; + const double *doublevar; + void *handle; + int (*intfunc)(int); + double (*doublefunc)(double, int); + + for (envptr = objs; *envptr != nullptr; ++envptr) { + envvar = getenv(*envptr); + EXPECT_NE(envvar, nullptr); + handle = platform::dlopen(envvar); + EXPECT_NE(handle, nullptr); + intvar = (int *) platform::dlsym(handle, "some_int_val"); + EXPECT_NE(intvar, nullptr); + EXPECT_EQ(*intvar, 12345); + doublevar = (double *) platform::dlsym(handle, "some_double_val"); + EXPECT_NE(doublevar, nullptr); + EXPECT_DOUBLE_EQ(*doublevar, 6.78e-9); + intfunc = (int (*)(int)) platform::dlsym(handle, "some_int_function"); + EXPECT_NE(intfunc, nullptr); + EXPECT_EQ((*intfunc)(12), 144); + doublefunc = (double (*)(double, int)) platform::dlsym(handle, "some_double_function"); + EXPECT_NE(doublefunc, nullptr); + EXPECT_DOUBLE_EQ((*doublefunc)(0.5, 6), 3.0); + EXPECT_EQ(platform::dlsym(handle, "some_nonexisting_symbol"), nullptr); + EXPECT_EQ(platform::dlclose(handle), 0); + } +} + +TEST(Platform, guesspath) +{ + char buf[256]; + FILE *fp = fopen("test_guesspath.txt", "w"); +#if defined(__linux__) || defined(__APPLE__) || defined(_WIN32) + const char *path = platform::guesspath(fp, buf, sizeof(buf)); + ASSERT_THAT(path, EndsWith("test_guesspath.txt")); +#else + const char *path = platform::guesspath(fp, buf, sizeof(buf)); + ASSERT_THAT(path, EndsWith("(unknown)")); +#endif + fclose(fp); + platform::unlink("test_guesspath.txt"); +} + +TEST(Platform, unlink) +{ + const char test[] = "12345678901234567890"; + platform::unlink("unlink.dat"); + ASSERT_EQ(platform::unlink("dummy.dat"), -1); + FILE *fp = fopen("unlink.dat", "w"); + fwrite(test, sizeof(test), 1, fp); + fclose(fp); + ASSERT_EQ(platform::unlink("unlink.dat"), 0); + ASSERT_EQ(platform::unlink("unlink.dat"), -1); + fp = fopen("unlink.dat", "r"); + ASSERT_EQ(fp, nullptr); + + platform::mkdir("unlink.dir"); + ASSERT_EQ(platform::unlink("unlink.dir"), -1); + platform::rmdir("unlink.dir"); +} + +TEST(Platform, fseek_ftell) +{ + const char test[] = "12345678901234567890"; + platform::unlink("seek_tell.dat"); + FILE *fp = fopen("seek_tell.dat", "w"); + fwrite(test, sizeof(test), 1, fp); + fflush(fp); + ASSERT_EQ(platform::ftell(fp), sizeof(test)); + fclose(fp); + fp = fopen("seek_tell.dat", "r+"); + ASSERT_EQ(fgetc(fp), '1'); + ASSERT_EQ(fgetc(fp), '2'); + ASSERT_EQ(platform::ftell(fp), 2); + ASSERT_EQ(platform::fseek(fp, 15), 0); + ASSERT_EQ(fgetc(fp), '6'); + fflush(fp); + fseek(fp, -1, SEEK_END); + ASSERT_EQ(fgetc(fp), 0); + ASSERT_EQ(platform::ftell(fp), 21); + fclose(fp); + platform::unlink("seek_tell.dat"); +} + +TEST(Platform, ftruncate) +{ + platform::unlink("truncate.dat"); + FILE *fp = fopen("truncate.dat", "w"); + fputs("header one\n", fp); + fputs("header two\n", fp); + fflush(fp); + bigint filepos = platform::ftell(fp); + fputs("line one\n", fp); + fputs("line two\n", fp); + fputs("line three\n", fp); + fflush(fp); + ASSERT_EQ(platform::ftruncate(fp, filepos), 0); + fputs("line four\n", fp); + ASSERT_GT(platform::ftell(fp), filepos); + fputs("line five\n", fp); + fflush(fp); + fclose(fp); + + // check file + fp = fopen("truncate.dat", "r"); + char buf[128]; + char *ptr = fgets(buf, 127, fp); + ASSERT_THAT(ptr, StartsWith("header one")); + ptr = fgets(buf, 127, fp); + ASSERT_THAT(ptr, StartsWith("header two")); + ptr = fgets(buf, 127, fp); + ASSERT_THAT(ptr, StartsWith("line four")); + ptr = fgets(buf, 127, fp); + ASSERT_THAT(ptr, StartsWith("line five")); + ptr = fgets(buf, 127, fp); + ASSERT_EQ(ptr, nullptr); + fclose(fp); + platform::unlink("truncate.dat"); +} + +TEST(Platform, path_basename) +{ +#if defined(_WIN32) + ASSERT_THAT(platform::path_basename("c:\\parent\\folder\\filename"), Eq("filename")); + ASSERT_THAT(platform::path_basename("folder\\"), Eq("")); + ASSERT_THAT(platform::path_basename("c:/parent/folder/filename"), Eq("filename")); +#else + ASSERT_THAT(platform::path_basename("/parent/folder/filename"), Eq("filename")); + ASSERT_THAT(platform::path_basename("/parent/folder/"), Eq("")); +#endif +} + +TEST(Platform, path_dirname) +{ +#if defined(_WIN32) + ASSERT_THAT(platform::path_dirname("c:/parent/folder/filename"), Eq("c:/parent/folder")); + ASSERT_THAT(platform::path_dirname("c:\\parent\\folder\\filename"), Eq("c:\\parent\\folder")); + ASSERT_THAT(platform::path_dirname("c:filename"), Eq(".")); +#else + ASSERT_THAT(platform::path_dirname("/parent/folder/filename"), Eq("/parent/folder")); +#endif + ASSERT_THAT(platform::path_dirname("filename"), Eq(".")); +} + +TEST(Platform, path_join) +{ +#if defined(_WIN32) + ASSERT_THAT(platform::path_join("c:\\folder", "filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder\\", "filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder", "\\filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder\\", "\\filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder", "/filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder\\\\", "\\filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder\\", "\\\\filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder/\\", "/\\filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder\\/", "\\/filename"), Eq("c:\\folder\\filename")); + ASSERT_THAT(platform::path_join("c:\\folder", ""), Eq("c:\\folder")); + ASSERT_THAT(platform::path_join("", "\\/filename"), Eq("\\/filename")); +#else + ASSERT_THAT(platform::path_join("/parent/folder", "filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder/", "filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder", "/filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder/", "/filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder//", "filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder", "//filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder///", "/filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder/", "///filename"), Eq("/parent/folder/filename")); + ASSERT_THAT(platform::path_join("/parent/folder/", ""), Eq("/parent/folder/")); + ASSERT_THAT(platform::path_join("", "\\/filename"), Eq("\\/filename")); +#endif +} + +TEST(Platform, is_console) +{ + platform::unlink("file_is_no_console.txt"); + FILE *fp = fopen("file_is_no_console.txt", "w"); + fputs("some text\n", fp); + EXPECT_FALSE(platform::is_console(fp)); + fclose(fp); + platform::unlink("file_is_no_console.txt"); +} + +TEST(Platform, path_and_directory) +{ + platform::unlink("path_is_directory"); + platform::rmdir("path_is_directory"); + platform::unlink("path_is_file"); + platform::mkdir("path_is_directory"); + FILE *fp = fopen("path_is_file", "w"); + fputs("some text\n", fp); + fclose(fp); + + ASSERT_TRUE(platform::path_is_directory("path_is_directory")); + ASSERT_FALSE(platform::path_is_directory("path_is_file")); + ASSERT_FALSE(platform::path_is_directory("path_does_not_exist")); + platform::unlink("path_is_file"); + +#if defined(_WIN32) + fp = fopen("path_is_directory\\path_is_file", "w"); +#else + fp = fopen("path_is_directory/path_is_file", "w"); +#endif + fputs("some text\n", fp); + fclose(fp); +#if defined(_WIN32) + platform::mkdir("path_is_directory\\path_is_directory"); + fp = fopen("path_is_directory\\path_is_other_file", "w"); +#else + platform::mkdir("path_is_directory/path_is_directory"); + fp = fopen("path_is_directory/path_is_other_file", "w"); +#endif + fputs("some text\n", fp); + fclose(fp); + auto dirs = platform::list_directory("path_is_directory"); + ASSERT_EQ(dirs.size(), 3); + platform::rmdir("path_is_directory"); + ASSERT_FALSE(platform::path_is_directory("path_is_directory")); +} + +TEST(Platform, get_change_directory) +{ + platform::unlink("working_directory"); + platform::rmdir("working_directory"); + + auto cwd = platform::current_directory(); + ASSERT_GT(cwd.size(), 0); + + platform::mkdir("working_directory"); + ASSERT_EQ(platform::chdir("working_directory"), 0); + ASSERT_THAT(platform::current_directory(), EndsWith("working_directory")); + + ASSERT_EQ(platform::chdir(".."), 0); + ASSERT_THAT(platform::current_directory(), StrEq(cwd)); + platform::rmdir("working_directory"); +} + +TEST(Platform, file_is_readable) +{ + platform::unlink("file_is_readable.txt"); + FILE *fp = fopen("file_is_readable.txt", "w"); + fputs("some text\n", fp); + fclose(fp); + + ASSERT_TRUE(platform::file_is_readable("file_is_readable.txt")); + ASSERT_FALSE(platform::file_is_readable("file_does_not_exist.txt")); + platform::unlink("file_is_readable.txt"); + + // windows does not have permission flags +#if !defined(_WIN32) + platform::unlink("file_is_not_readable.txt"); + fp = fopen("file_is_not_readable.txt", "w"); + fputs("some text\n", fp); + fclose(fp); + chmod("file_is_not_readable.txt", 0); + ASSERT_FALSE(platform::file_is_readable("file_is_not_readable.txt")); + platform::unlink("file_is_not_readable.txt"); +#endif +} + +TEST(Platform, has_zip_extension) +{ + ASSERT_FALSE(platform::has_zip_extension("dummy")); + ASSERT_FALSE(platform::has_zip_extension("dum.my")); + ASSERT_TRUE(platform::has_zip_extension("dummy.gz")); + ASSERT_TRUE(platform::has_zip_extension("dummy.bz2")); + ASSERT_TRUE(platform::has_zip_extension("dummy.zstd")); + ASSERT_TRUE(platform::has_zip_extension("dummy.xz")); + ASSERT_TRUE(platform::has_zip_extension("dummy.lzma")); + ASSERT_TRUE(platform::has_zip_extension("dummy.lz4")); +} + +TEST(Platform, zip_read_write) +{ + const std::vector test_files = {"zip_test.zip", "zip_test.gz", "zip_test.bz2", + "zip_test.zstd", "zip_test.xz", "zip_test.lzma", + "zip_test.lz4", "zip_test.unk", "zip test.gz"}; + for (const auto &file : test_files) { + platform::unlink(file); + FILE *fp = platform::zip_write(file); + if (!fp) { + platform::unlink(file); + continue; + } + + clearerr(fp); + fputs("line one\n", fp); + fputs("line two\n", fp); + ASSERT_EQ(ferror(fp), 0); + fflush(fp); + platform::pclose(fp); + + fp = platform::zip_read(file); + ASSERT_NE(fp, nullptr); + char buf[128]; + char *ptr = fgets(buf, 128, fp); + EXPECT_THAT(ptr, StartsWith("line one")); + ptr = fgets(buf, 128, fp); + EXPECT_THAT(ptr, StartsWith("line two")); + ASSERT_EQ(ferror(fp), 0); + platform::pclose(fp); + platform::unlink(file); + } +} diff --git a/unittest/utils/testshared.c b/unittest/utils/testshared.c new file mode 100644 index 0000000000..869be91c2a --- /dev/null +++ b/unittest/utils/testshared.c @@ -0,0 +1,20 @@ +/* file for testing loading of shared objects and libraries */ + +int some_int_val = 12345; +double some_double_val = 6.78e-9; + +int some_int_function(int arg) +{ + return arg*arg; +} + +double some_double_function(double arg1, int arg2) +{ + double sum = 0; + for (int i = 0; i < arg2; ++i) + sum += arg1; + return sum; +} + + +