sudo apt-get update
# Install ld.gold (binutils) and ld.lld on different runs.
- # Binding to Ubuntu 20 has no special meaning.
- if [ "${{ matrix.config.os }}" = "ubuntu-20.04" ]; then
- sudo apt-get install -y ninja-build elfutils libzstd-dev lld
- else
+ if [ "${{ matrix.config.os }}" = "ubuntu-16.04" ]; then
sudo apt-get install -y ninja-build elfutils libzstd1-dev binutils
+ else
+ sudo apt-get install -y ninja-build elfutils libzstd-dev lld
fi
if [ "${{ matrix.config.compiler }}" = "gcc" ]; then
BUILDDIR: .
CCACHE_LOC: .
CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=Debug -DENABLE_TRACING=1 -DCMAKE_CXX_STANDARD=14
- apt_get: elfutils libzstd1-dev
+ apt_get: elfutils libzstd-dev
- name: Linux GCC 32-bit
os: ubuntu-18.04
CMAKE_PARAMS: -DCMAKE_BUILD_TYPE=CI -DZSTD_FROM_INTERNET=ON
ENABLE_CACHE_CLEANUP_TESTS: 1
CUDA: 10.1.243-1
- apt_get: elfutils libzstd1-dev
+ apt_get: elfutils libzstd-dev
- name: Linux MinGW 32-bit
os: ubuntu-18.04
os: ubuntu-18.04
EXTRA_CMAKE_BUILD_FLAGS: --target doc-html
RUN_TESTS: none
- apt_get: libzstd1-dev asciidoc docbook-xml docbook-xsl
+ apt_get: libzstd-dev asciidoc docbook-xml docbook-xsl
- name: Manual page
os: ubuntu-18.04
EXTRA_CMAKE_BUILD_FLAGS: --target doc-man-page
RUN_TESTS: none
- apt_get: libzstd1-dev asciidoc xsltproc docbook-xml docbook-xsl
+ apt_get: libzstd-dev asciidoc xsltproc docbook-xml docbook-xsl
- name: Clang-Tidy
os: ubuntu-18.04
fetch-depth: 2
- name: Install dependencies
- run: sudo apt-get update && sudo apt-get install ninja-build elfutils libzstd1-dev
+ run: sudo apt-get update && sudo apt-get install ninja-build elfutils libzstd-dev
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
Martin Ettl <ettl.martin78@gmail.com>
Mizuha Himuraki <mocha.java.cchip@gmail.com>
Paul Bunch <paulbunc@gmail.com>
+Pawel Krysiak <pawel_krysiak@interia.pl>
Per Nordlöw <per.nordlow@autoliv.com>
Peter Budai <peterbudai@hotmail.com>
Ramiro Polla <ramiro.polla@gmail.com>
message(STATUS "Ccache dev mode: ${CCACHE_DEV_MODE}")
include(UseCcache)
-include(UseFastestLinker)
+if(NOT MSVC)
+ include(UseFastestLinker)
+endif()
include(StandardSettings)
include(StandardWarnings)
include(CIBuildType)
~~~~~~~~~~~~~~~~~~~~~~~~~
This is the single header version of https://github.com/onqtam/doctest[doctest]
-2.4.4 with the following license:
+2.4.6 with the following license:
-------------------------------------------------------------------------------
The MIT License (MIT)
-Copyright (c) 2016-2020 Viktor Kirilov
+Copyright (c) 2016-2021 Viktor Kirilov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-------------------------------------------------------------------------------
+src/third_party/win32/winerror_to_errno.h
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The implementation of `winerror_to_errno()` was adapted from
+<https://github.com/python/cpython/blob/1a79785e3e8fea80bcf6a800b45a04e06c787480/PC/errmap.h>
+and has the folowing license text:
+
+-------------------------------------------------------------------------------
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+-------------------------------------------------------------------------------
+
src/third_party/xxh*
~~~~~~~~~~~~~~~~~~~~
# CCACHE_VERSION_ORIGIN is set to "archive" in scenario 1 and "git" in scenario
# 3.
-set(version_info "12ecd73fcd8aa7024d5851c1738223b8aff0c6e9 HEAD, tag: v4.2, origin/master, origin/HEAD, master")
+set(version_info "1a07c09dcbba202572c5ab1bc4b736183a318aa7 HEAD, tag: v4.2.1, origin/master, origin/HEAD, master")
if(version_info MATCHES "^([0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f])[0-9a-f]* (.*)")
# Scenario 1.
# Although ${zstd_FIND_VERSION} was requested, let's download a newer version.
# Note: The directory structure has changed in 1.3.0; we only support 1.3.0
# and newer.
- set(zstd_version "1.4.8")
+ set(zstd_version "1.4.9")
set(zstd_url https://github.com/facebook/zstd/archive/v${zstd_version}.tar.gz)
set(zstd_dir ${CMAKE_BINARY_DIR}/zstd-${zstd_version})
int main()
{
std::atomic<long long> x;
+ ++x;
(void)x.load();
return 0;
}
endif()
endfunction()
+if(MSVC)
+ # Ccache does not support cl.exe-style arguments at this time.
+ return()
+endif()
+
option(USE_CCACHE "Use ccache to speed up recompilation time" TRUE)
if(USE_CCACHE)
use_ccache()
-# Calls `message(VERBOSE msg)` if and only if VERBOSE is available (since CMake 3.15).
-# Call CMake with --loglevel=VERBOSE to view those messages.
-function(message_verbose msg)
- if(NOT ${CMAKE_VERSION} VERSION_LESS "3.15")
- message(VERBOSE ${msg})
- endif()
+function(check_linker linker)
+ string(TOUPPER ${linker} upper_linker)
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakefiles/CMakeTmp/main.c" "int main() { return 0; }")
+ try_compile(
+ HAVE_LD_${upper_linker}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ "${CMAKE_CURRENT_BINARY_DIR}/CMakefiles/CMakeTmp/main.c"
+ LINK_LIBRARIES "-fuse-ld=${linker}"
+ )
endfunction()
function(use_fastest_linker)
message(WARNING "use_fastest_linker() disabled, as it is not called at the project top level")
return()
endif()
-
- find_program(FASTER_LINKER ld.lld)
- if(NOT FASTER_LINKER)
- find_program(FASTER_LINKER ld.gold)
- endif()
-
- if(FASTER_LINKER)
- # Note: Compiler flag -fuse-ld requires gcc 9 or clang 3.8.
- # Instead override CMAKE_CXX_LINK_EXECUTABLE directly.
- # By default CMake uses the compiler executable for linking.
- set(CMAKE_CXX_LINK_EXECUTABLE "${FASTER_LINKER} <FLAGS> <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <OBJECTS> -o <TARGET> <LINK_LIBRARIES>")
- message_verbose("Using ${FASTER_LINKER} linker for faster linking")
+
+ set(use_default_linker 1)
+ check_linker(lld)
+ if(HAVE_LD_LLD)
+ link_libraries("-fuse-ld=lld")
+ set(use_default_linker 0)
+ message(STATUS "Using lld linker")
else()
- message_verbose("Using default linker")
+ check_linker(gold)
+ if(HAVE_LD_GOLD)
+ link_libraries("-fuse-ld=gold")
+ set(use_default_linker 0)
+ message(STATUS "Using gold linker")
+ endif()
+ endif()
+ if(use_default_linker)
+ message(STATUS "Using default linker")
endif()
endfunction()
# define _XOPEN_SOURCE 500
#elif defined(__FreeBSD__)
# define _XOPEN_SOURCE 700
+#elif defined(__ibmxl__) && defined(__clang__) // Compiler xlclang
+# define _XOPEN_SOURCE 600
+# define _ALL_SOURCE 1
#elif !defined(__SunOS_5_11) && !defined(__APPLE__)
# define _XOPEN_SOURCE
#endif
// Define if you have the "PTHREAD_MUTEX_ROBUST" constant.
#cmakedefine HAVE_PTHREAD_MUTEX_ROBUST
+
+#if defined(__ibmxl__) && defined(__clang__) // Compiler xlclang
+# undef HAVE_VARARGS_H // varargs.h would hide macros of stdarg.h
+# undef HAVE_STRUCT_STAT_ST_CTIM
+# undef HAVE_STRUCT_STAT_ST_MTIM
+#endif
Ccache is a collective work with contributions from many people, including:
+* Abubakar Nur Khalil
+* Aleksander Salwa
* Alexander Korsunsky
* Alexander Lanin
* Alexey Tourbin
* Edward Z. Yang
* Enrico Sorichetti
* Erik Flodin
+* Evangelos Foutras
* Francois Marier
* Gabriel Scherer
* Geert Bosch
* John Coiner
* Jon Bernard
* Jonny Yu
+* Jon Petrissans
* Jørgen P. Tjernø
* Josh Soref
* Justin Lebar
* Paul Griffith
* Pavel Boldin
* Pavol Sakac
+* Pawel Krysiak
* Per Nordlöw
* Peter Budai
* Philippe Proulx
mark_as_advanced(ASCIIDOC_EXE) # Don't show in CMake UIs
if(NOT ASCIIDOC_EXE)
- message(NOTICE "Could not find asciidoc; documentation will not be generated")
+ message(WARNING "Could not find asciidoc; documentation will not be generated")
else()
#
# HTML documentation
languages](https://ccache.dev/platform-compiler-language-support.html) for
details.
- A C99 compiler.
-- [libzstd](https://www.zstd.net). If you don't have libzstd installed and
+- [libzstd](http://www.zstd.net). If you don't have libzstd installed and
can't or don't want to install it in a standard system location, there are
two options:
1. Install zstd in a custom path and set `CMAKE_PREFIX_PATH` to it, e.g.
GCC-based compiler.
*nvcc*::
NVCC (CUDA) compiler.
-*other::
+*other*::
Any compiler other than the known types.
*pump*::
distcc's "pump" script.
next to the object file. If set to a directory, the debug files will be
written with full absolute paths in that directory, creating it if needed.
The default is the empty string.
-
- For example, if *debug_dir* is set to `/example`, the current working
- directory is `/home/user` and the object file is `build/output.o` then the
- debug log will be written to `/example/home/user/build/output.o.ccache-log`.
- See also _<<_cache_debugging,Cache debugging>>_.
++
+For example, if *debug_dir* is set to `/example`, the current working directory
+is `/home/user` and the object file is `build/output.o` then the debug log will
+be written to `/example/home/user/build/output.o.ccache-log`. See also
+_<<_cache_debugging,Cache debugging>>_.
[[config_depend_mode]] *depend_mode* (*CCACHE_DEPEND* or *CCACHE_NODEPEND*, see _<<_boolean_values,Boolean values>>_ above)::
| error hashing extra file |
Failure reading a file specified by
-<<config_extra_file_to_hash,*extra_files_to_hash*>> (*CCACHE_EXTRAFILES*).
+<<config_extra_files_to_hash,*extra_files_to_hash*>> (*CCACHE_EXTRAFILES*).
| files in cache |
Current number of files in the cache.
* the name of the compiler
* the current directory (if <<config_hash_dir,*hash_dir*>> is enabled)
* contents of files specified by
- <<config_extra_file_to_hash,*extra_files_to_hash*>> (if any)
+ <<config_extra_files_to_hash,*extra_files_to_hash*>> (if any)
The preprocessor mode
Ccache news
===========
+Ccache 4.2.1
+------------
+Release date: 2021-03-27
+
+Bug fixes
+~~~~~~~~~
+
+- Ccache now only `dup2`s stderr into `$UNCACHED_ERR_FD` for calls to the
+ preprocessor/compiler. This works around a complex bug in interaction with GNU
+ Make, LTO linking and the Linux PTY driver.
+
+- Fixed detection of color diagnostics usage when using `-Xclang
+ -fcolor-diagnostics` options.
+
+- The `-frecord-gcc-switches` compiler option is now handled correctly to avoid
+ false positive cache hits.
+
+- Made it possible for per-compilation debug log files to be written in most
+ argument processing error scenarios. Previously, ccache would only write debug
+ log files if the argument processing phase was successful.
+
+- Made ccache bail out on too hard Clang option `-gen-cdb-fragment-path`.
+
+- The `run_second_cpp` made is now enforced on macOS if `-g` is used since newer
+ Clang versions on macOS produce different debug information when compiling
+ preprocessed code.
+
+- Made ccache only reject `-f(no-)color-diagnostics` for a known GCC compiler.
+ This fixes a problem when using said option with Clang on macOS.
+
+- Implemented a better `stat`/`lstat` wrapper function for Windows.
+
+- Fixed a bug where ccache could return stale cache results on Windows.
+
+- Fixed handling of long command lines on Windows.
+
+
+Portability and build improvements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Build configuration scripts now probe for atomic increment as well. This fixes
+ a linking error on Sparc.
+
+- An existing CMake log message level is now used when warning about not finding
+ asciidoc.
+
+- Added support for building ccache with xlclang++ on AIX 7.2.
+
+- Fixed assertion in the "Debug option" test.
+
+- Made build configuration skip using ccache when building with MSVC.
+
+- Upgraded to doctest 2.4.6. This fixes a build error with glibc >= 2.34.
+
+
+Documentation
+~~~~~~~~~~~~~
+
+- Fixed markup of `compiler_type` value "other".
+
+- Fixed markup of `debug_dir` documentation.
+
+- Fixed references to the `extra_files_to_hash` configuration option.
+
+
Ccache 4.2
----------
Release date: 2021-02-02
- The value of the `SOURCE_DATE_EPOCH` variable is now only hashed if it
potentially affects the output from ccache. This means that ccache now (like
- before version 4.0) will be able produce cache hits for source code that
+ before version 4.0) will be able to produce cache hits for source code that
doesn't contain `__DATE__` or `__TIME__` macros regardless of the value of
`SOURCE_DATE_EPOCH`.
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "third_party/fmt/core.h"
CacheEntryReader::CacheEntryReader(FILE* stream,
- const uint8_t expected_magic[4],
+ const uint8_t* expected_magic,
uint8_t expected_version)
{
uint8_t header_bytes[15];
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
//
// Parameters:
// - stream: Stream to read header and payload from.
- // - expected_magic: Expected magic bytes (first four bytes of the file).
+ // - expected_magic: Expected file format magic (first four bytes of the
+ // file).
// - expected_version: Expected file format version.
CacheEntryReader(FILE* stream,
- const uint8_t expected_magic[4],
+ const uint8_t* expected_magic,
uint8_t expected_version);
// Dump header information in text format.
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "CacheEntryWriter.hpp"
CacheEntryWriter::CacheEntryWriter(FILE* stream,
- const uint8_t magic[4],
+ const uint8_t* magic,
uint8_t version,
Compression::Type compression_type,
int8_t compression_level,
-// Copyright (C) 2019-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2019-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
//
// Parameters:
// - stream: Stream to write header + payload to.
- // - magic: File format magic bytes.
+ // - magic: File format magic (first four bytes of the file).
// - version: File format version.
// - compression_type: Compression type to use.
// - compression_level: Compression level to use.
// - payload_size: Payload size.
CacheEntryWriter(FILE* stream,
- const uint8_t magic[4],
+ const uint8_t* magic,
uint8_t version,
Compression::Type compression_type,
int8_t compression_level,
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
case '$':
if (p + 1 < length) {
const char next = file_content[p + 1];
- switch (next) {
- // A dollar sign preceded by a dollar sign escapes the dollar sign.
- case '$':
+ if (next == '$') {
+ // A dollar sign preceded by a dollar sign escapes the dollar sign.
c = next;
++p;
- break;
}
}
break;
#include "Stat.hpp"
+#ifdef _WIN32
+# include "third_party/win32/winerror_to_errno.h"
+#endif
+
+#include "Finalizer.hpp"
#include "Logging.hpp"
+namespace {
+
+#ifdef _WIN32
+
+uint16_t
+win32_file_attributes_to_stat_mode(DWORD attr)
+{
+ uint16_t m = 0;
+ if (attr & FILE_ATTRIBUTE_DIRECTORY) {
+ m |= S_IFDIR | 0111;
+ } else {
+ m |= S_IFREG;
+ }
+ if (attr & FILE_ATTRIBUTE_READONLY) {
+ m |= 0444;
+ } else {
+ m |= 0666;
+ }
+ return m;
+}
+
+struct timespec
+win32_filetime_to_timespec(FILETIME ft)
+{
+ static const int64_t SECS_BETWEEN_EPOCHS = 11644473600;
+ uint64_t v =
+ (static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+ struct timespec ts = {};
+ ts.tv_sec = (v / 10000000) - SECS_BETWEEN_EPOCHS;
+ ts.tv_nsec = (v % 10000000) * 100;
+ return ts;
+}
+
+void
+win32_file_information_to_stat(const BY_HANDLE_FILE_INFORMATION& file_info,
+ const FILE_ATTRIBUTE_TAG_INFO& reparse_info,
+ const char* path,
+ Stat::stat_t* st)
+{
+ st->st_dev = file_info.dwVolumeSerialNumber;
+ st->st_ino = (static_cast<uint64_t>(file_info.nFileIndexHigh) << 32)
+ | file_info.nFileIndexLow;
+ st->st_mode = win32_file_attributes_to_stat_mode(file_info.dwFileAttributes);
+ st->st_nlink = file_info.nNumberOfLinks;
+ st->st_size = (static_cast<uint64_t>(file_info.nFileSizeHigh) << 32)
+ | file_info.nFileSizeLow;
+ st->st_atim = win32_filetime_to_timespec(file_info.ftLastAccessTime);
+ st->st_mtim = win32_filetime_to_timespec(file_info.ftLastWriteTime);
+ st->st_ctim = win32_filetime_to_timespec(file_info.ftCreationTime);
+ st->st_file_attributes = file_info.dwFileAttributes;
+ st->st_reparse_tag = reparse_info.ReparseTag;
+
+ if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ && IsReparseTagNameSurrogate(reparse_info.ReparseTag)) {
+ // Don't consider name surrogate reparse points (symlinks and junctions) as
+ // regular files or directories.
+ st->st_mode &= ~S_IFMT;
+ // Set S_IFLNK bit if this is a Windows symlink.
+ st->st_mode |=
+ (reparse_info.ReparseTag == IO_REPARSE_TAG_SYMLINK) ? S_IFLNK : 0;
+ }
+
+ // Add the executable permission using the same goofy logic used by
+ // Microsoft's C runtime. See ucrt\filesystem\stat.cpp in the Windows 10 SDK.
+ if (!(file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ if (const char* file_extension = strchr(path, '.')) {
+ if (_stricmp(file_extension, ".exe") == 0
+ || _stricmp(file_extension, ".bat") == 0
+ || _stricmp(file_extension, ".cmd") == 0
+ || _stricmp(file_extension, ".com") == 0) {
+ st->st_mode |= 0111;
+ }
+ }
+ }
+}
+
+bool
+win32_stat_impl(const char* path, bool traverse_links, Stat::stat_t* st)
+{
+ *st = {};
+
+ DWORD access = FILE_READ_ATTRIBUTES;
+ DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
+ DWORD flags = traverse_links
+ ? FILE_FLAG_BACKUP_SEMANTICS
+ : FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT;
+
+ HANDLE handle = CreateFileA(
+ path, access, share_mode, nullptr, OPEN_EXISTING, flags, nullptr);
+ if (handle == INVALID_HANDLE_VALUE
+ && GetLastError() == ERROR_INVALID_PARAMETER) {
+ // For some special paths (e.g. "CON") FILE_READ_ATTRIBUTES is insufficient.
+ access |= GENERIC_READ;
+ handle = CreateFileA(
+ path, access, share_mode, nullptr, OPEN_EXISTING, flags, nullptr);
+ }
+
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ Finalizer closer([&] { CloseHandle(handle); });
+
+ switch (GetFileType(handle)) {
+ case FILE_TYPE_DISK: {
+ FILE_ATTRIBUTE_TAG_INFO reparse_info = {};
+ if (!traverse_links
+ && GetFileInformationByHandleEx(
+ handle, FileAttributeTagInfo, &reparse_info, sizeof(reparse_info))
+ && (reparse_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ && !IsReparseTagNameSurrogate(reparse_info.ReparseTag)) {
+ // We've opened a non name-surrogate reparse point. These types
+ // of reparse points should be followed even by lstat().
+ return win32_stat_impl(path, true, st);
+ }
+
+ BY_HANDLE_FILE_INFORMATION file_info = {};
+ if (GetFileInformationByHandle(handle, &file_info)) {
+ win32_file_information_to_stat(file_info, reparse_info, path, st);
+ } else if (GetLastError() == ERROR_INVALID_FUNCTION) {
+ st->st_mode |= S_IFBLK;
+ } else {
+ return false;
+ }
+ return true;
+ }
+
+ case FILE_TYPE_CHAR:
+ st->st_mode = S_IFCHR;
+ return true;
+
+ case FILE_TYPE_PIPE:
+ st->st_mode = S_IFIFO;
+ return true;
+
+ case FILE_TYPE_UNKNOWN:
+ default:
+ return true;
+ }
+}
+
+int
+win32_stat(const char* path, Stat::stat_t* st)
+{
+ bool ok = win32_stat_impl(path, true, st);
+ if (ok) {
+ return 0;
+ }
+ errno = winerror_to_errno(GetLastError());
+ return -1;
+}
+
+int
+win32_lstat(const char* path, Stat::stat_t* st)
+{
+ bool ok = win32_stat_impl(path, false, st);
+ if (ok) {
+ return 0;
+ }
+ errno = winerror_to_errno(GetLastError());
+ return -1;
+}
+
+#endif // _WIN32
+
+} // namespace
+
Stat::Stat(StatFunction stat_function,
const std::string& path,
Stat::OnError on_error)
memset(&m_stat, '\0', sizeof(m_stat));
}
}
+
+Stat
+Stat::stat(const std::string& path, OnError on_error)
+{
+ return Stat(
+#ifdef _WIN32
+ win32_stat,
+#else
+ ::stat,
+#endif
+ path,
+ on_error);
+}
+
+Stat
+Stat::lstat(const std::string& path, OnError on_error)
+{
+ return Stat(
+#ifdef _WIN32
+ win32_lstat,
+#else
+ ::lstat,
+#endif
+ path,
+ on_error);
+}
throw_error,
};
+#if defined(_WIN32)
+ struct stat_t
+ {
+ uint64_t st_dev;
+ uint64_t st_ino;
+ uint16_t st_mode;
+ uint16_t st_nlink;
+ uint64_t st_size;
+ struct timespec st_atim;
+ struct timespec st_mtim;
+ struct timespec st_ctim;
+ uint32_t st_file_attributes;
+ uint32_t st_reparse_tag;
+ };
+#else
+ // Use of typedef needed to suppress a spurious 'declaration does not declare
+ // anything' warning in old GCC.
+ typedef struct ::stat stat_t; // NOLINT(modernize-use-using)
+#endif
+
+ using dev_t = decltype(stat_t{}.st_dev);
+ using ino_t = decltype(stat_t{}.st_ino);
+
// Create an empty stat result. operator bool() will return false,
// error_number() will return -1 and other accessors will return false or 0.
Stat();
bool is_regular() const;
bool is_symlink() const;
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
- timespec ctim() const;
+#ifdef _WIN32
+ uint32_t file_attributes() const;
+ uint32_t reparse_tag() const;
#endif
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
+ timespec ctim() const;
timespec mtim() const;
-#endif
protected:
- using StatFunction = int (*)(const char*, struct stat*);
+ using StatFunction = int (*)(const char*, stat_t*);
Stat(StatFunction stat_function, const std::string& path, OnError on_error);
private:
- struct stat m_stat;
+ stat_t m_stat;
int m_errno;
bool operator==(const Stat&) const;
{
}
-inline Stat
-Stat::stat(const std::string& path, OnError on_error)
-{
- return Stat(::stat, path, on_error);
-}
-
-inline Stat
-Stat::lstat(const std::string& path, OnError on_error)
-{
- return Stat(
-#ifdef _WIN32
- ::stat,
-#else
- ::lstat,
-#endif
- path,
- on_error);
-}
-
inline Stat::operator bool() const
{
return m_errno == 0;
return m_errno;
}
-inline dev_t
+inline Stat::dev_t
Stat::device() const
{
return m_stat.st_dev;
}
-inline ino_t
+inline Stat::ino_t
Stat::inode() const
{
return m_stat.st_ino;
inline time_t
Stat::ctime() const
{
- return m_stat.st_ctime;
+ return ctim().tv_sec;
}
inline time_t
Stat::mtime() const
{
- return m_stat.st_mtime;
+ return mtim().tv_sec;
}
inline uint64_t
inline bool
Stat::is_symlink() const
{
-#ifndef _WIN32
return S_ISLNK(mode());
-#else
- return false;
-#endif
}
inline bool
return S_ISREG(mode());
}
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
+#ifdef _WIN32
+inline uint32_t
+Stat::file_attributes() const
+{
+ return m_stat.st_file_attributes;
+}
+
+inline uint32_t
+Stat::reparse_tag() const
+{
+ return m_stat.st_reparse_tag;
+}
+#endif
+
inline timespec
Stat::ctim() const
{
+#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_CTIM)
return m_stat.st_ctim;
-}
+#else
+ return {m_stat.st_ctime, 0};
#endif
+}
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
inline timespec
Stat::mtim() const
{
+#if defined(_WIN32) || defined(HAVE_STRUCT_STAT_ST_MTIM)
return m_stat.st_mtim;
-}
+#else
+ return {m_stat.st_mtime, 0};
#endif
+}
result.resize(size_hint);
while (true) {
- if (pos > result.size()) {
+ if (pos == result.size()) {
result.resize(2 * result.size());
}
const size_t max_read = result.size() - pos;
}
std::string
-argv_to_string(const char* const* argv, const std::string& prefix)
+argv_to_string(const char* const* argv,
+ const std::string& prefix,
+ bool escape_backslashes)
{
std::string result;
size_t i = 0;
for (size_t j = 0; arg[j]; ++j) {
switch (arg[j]) {
case '\\':
- ++bs;
- break;
- // Fallthrough.
+ if (!escape_backslashes) {
+ ++bs;
+ break;
+ }
+ // Fallthrough.
case '"':
bs = (bs << 1) + 1;
// Fallthrough.
-// Copyright (C) 2020 Joel Rosdahl and other contributors
+// Copyright (C) 2020-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
std::string add_exe_suffix(const std::string& program);
// Recreate a Windows command line string based on `argv`. If `prefix` is
-// non-empty, add it as the first argument.
-std::string argv_to_string(const char* const* argv, const std::string& prefix);
+// non-empty, add it as the first argument. If `escape_backslashes` is true,
+// emit an additional backslash for each backslash that is not preceding '"' and
+// is not at the end of `argv[i]` either.
+std::string argv_to_string(const char* const* argv,
+ const std::string& prefix,
+ bool escape_backslashes = false);
// Return the error message corresponding to `error_code`.
std::string error_message(DWORD error_code);
// the compiler, not the preprocessor, and that also should not be part of the
// hash identifying the result.
Args compiler_only_args_no_hash;
+
+ // Whether to include the full command line in the hash.
+ bool hash_full_command_line = false;
};
bool
return nullopt;
}
- if (config.compiler_type() != CompilerType::clang
+ if (config.compiler_type() == CompilerType::gcc
&& (args[i] == "-fcolor-diagnostics"
|| args[i] == "-fno-color-diagnostics")) {
- // Special case: If a non-Clang compiler gets -f(no-)color-diagnostics we'll
- // bail out and just execute the compiler. The reason is that we don't
- // include -f(no-)color-diagnostics in the hash so there can be a false
- // cache hit in the following scenario:
+ // Special case: If a GCC compiler gets -f(no-)color-diagnostics we'll bail
+ // out and just execute the compiler. The reason is that we don't include
+ // -f(no-)color-diagnostics in the hash so there can be a false cache hit in
+ // the following scenario:
//
// 1. ccache gcc -c example.c # adds a cache entry
// 2. ccache gcc -c example.c -fcolor-diagnostics # unexpectedly succeeds
return Statistic::unsupported_compiler_option;
}
+ // In the "-Xclang -fcolor-diagnostics" form, -Xclang is skipped and the
+ // -fcolor-diagnostics argument which is passed to cc1 is handled below.
+ if (args[i] == "-Xclang" && i < args.size() - 1
+ && args[i + 1] == "-fcolor-diagnostics") {
+ state.compiler_only_args_no_hash.push_back(args[i]);
+ ++i;
+ }
+
if (args[i] == "-fcolor-diagnostics" || args[i] == "-fdiagnostics-color"
|| args[i] == "-fdiagnostics-color=always") {
state.color_diagnostics = ColorDiagnostics::always;
return nullopt;
}
+ if (args[i] == "-frecord-gcc-switches") {
+ state.hash_full_command_line = true;
+ }
+
// Options taking an argument that we may want to rewrite to relative paths to
// get better hit rate. A secondary effect is that paths in the standard error
// output produced by the compiler will be normalized.
state.common_args.push_back(args[0]); // Compiler
+ optional<Statistic> argument_error;
for (size_t i = 1; i < args.size(); i++) {
- auto error = process_arg(ctx, args, i, state);
- if (error) {
- return *error;
+ const auto error = process_arg(ctx, args, i, state);
+ if (error && !argument_error) {
+ argument_error = error;
}
}
+ // Don't try to second guess the compiler's heuristics for stdout handling.
+ if (args_info.output_obj == "-") {
+ LOG_RAW("Output file is -");
+ return Statistic::output_to_stdout;
+ }
+
+ // Determine output object file.
+ const bool implicit_output_obj = args_info.output_obj.empty();
+ if (implicit_output_obj && !args_info.input_file.empty()) {
+ string_view extension = state.found_S_opt ? ".s" : ".o";
+ args_info.output_obj =
+ Util::change_extension(Util::base_name(args_info.input_file), extension);
+ }
+
+ // On argument processing error, return now since we have determined
+ // args_info.output_obj which is needed to determine the log filename in
+ // CCACHE_DEBUG mode.
+ if (argument_error) {
+ return *argument_error;
+ }
+
if (state.generating_debuginfo_level_3 && !config.run_second_cpp()) {
+ // Debug level 3 makes line number information incorrect when compiling
+ // preprocessed code.
LOG_RAW("Generating debug info level 3; not compiling preprocessed code");
config.set_run_second_cpp(true);
}
+#ifdef __APPLE__
+ // Newer Clang versions on macOS are known to produce different debug
+ // information when compiling preprocessed code.
+ if (args_info.generating_debuginfo && !config.run_second_cpp()) {
+ LOG_RAW("Generating debug info; not compiling preprocessed code");
+ config.set_run_second_cpp(true);
+ }
+#endif
+
handle_dependency_environment_variables(ctx, state);
if (args_info.input_file.empty()) {
args_info.actual_language.find("-header") != std::string::npos
|| Util::is_precompiled_header(args_info.output_obj);
+ if (args_info.output_is_precompiled_header && implicit_output_obj) {
+ args_info.output_obj = args_info.input_file + ".gch";
+ }
+
if (args_info.output_is_precompiled_header
&& !(config.sloppiness() & SLOPPY_PCH_DEFINES)) {
LOG_RAW(
config.set_cpp_extension(extension_for_language(p_language).substr(1));
}
- // Don't try to second guess the compilers heuristics for stdout handling.
- if (args_info.output_obj == "-") {
- LOG_RAW("Output file is -");
- return Statistic::output_to_stdout;
- }
-
- if (args_info.output_obj.empty()) {
- if (args_info.output_is_precompiled_header) {
- args_info.output_obj = args_info.input_file + ".gch";
- } else {
- string_view extension = state.found_S_opt ? ".s" : ".o";
- args_info.output_obj = Util::change_extension(
- Util::base_name(args_info.input_file), extension);
- }
- }
-
if (args_info.seen_split_dwarf) {
args_info.output_dwo = Util::change_extension(args_info.output_obj, ".dwo");
}
if (config.run_second_cpp()) {
extra_args_to_hash.push_back(state.dep_args);
}
+ if (state.hash_full_command_line) {
+ extra_args_to_hash.push_back(ctx.orig_args);
+ }
if (diagnostics_color_arg) {
compiler_args.push_back(*diagnostics_color_arg);
#elif defined(_WIN32)
# include "third_party/win32/getopt.h"
#else
+extern "C" {
# include "third_party/getopt_long.h"
+}
#endif
#ifdef _WIN32
DEBUG_ASSERT(ctx.config.compiler_type() == CompilerType::gcc);
args.erase_last("-fdiagnostics-color");
}
- int status = execute(args.to_argv().data(),
+ int status = execute(ctx,
+ args.to_argv().data(),
std::move(tmp_stdout.fd),
- std::move(tmp_stderr.fd),
- &ctx.compiler_pid);
+ std::move(tmp_stderr.fd));
if (status != 0 && !ctx.diagnostics_color_failed
&& ctx.config.compiler_type() == CompilerType::gcc) {
auto errors = Util::read_file(tmp_stderr.path);
bool fall_back_to_original_compiler = false;
Args saved_orig_args;
nonstd::optional<mode_t> original_umask;
+ std::string saved_temp_dir;
{
Context ctx;
LOG_RAW("Failed; falling back to running the real compiler");
+ saved_temp_dir = ctx.config.temporary_dir();
saved_orig_args = std::move(ctx.orig_args);
auto execv_argv = saved_orig_args.to_argv();
LOG("Executing {}", Util::format_argv_for_logging(execv_argv.data()));
- // Run execv below after ctx and finalizer have been destructed.
+ // Execute the original command below after ctx and finalizer have been
+ // destructed.
}
}
umask(*original_umask);
}
auto execv_argv = saved_orig_args.to_argv();
- execv(execv_argv[0], const_cast<char* const*>(execv_argv.data()));
- throw Fatal("execv of {} failed: {}", execv_argv[0], strerror(errno));
+ execute_noreturn(execv_argv.data(), saved_temp_dir);
+ throw Fatal(
+ "execute_noreturn of {} failed: {}", execv_argv[0], strerror(errno));
}
return EXIT_SUCCESS;
throw Failure(Statistic::cache_miss);
}
- MTR_BEGIN("main", "set_up_uncached_err");
- set_up_uncached_err();
- MTR_END("main", "set_up_uncached_err");
-
LOG("Command line: {}", Util::format_argv_for_logging(argv));
LOG("Hostname: {}", Util::get_hostname());
LOG("Working directory: {}", ctx.actual_cwd);
throw Failure(*processed.error);
}
+ set_up_uncached_err();
+
if (ctx.config.depend_mode()
&& (!ctx.args_info.generating_dependencies
|| ctx.args_info.output_dep == "/dev/null"
-// Copyright (C) 2010-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2010-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
{"-frepo", TOO_HARD},
{"-ftime-trace", TOO_HARD}, // Clang
{"-fworking-directory", AFFECTS_CPP},
+ {"-gen-cdb-fragment-path", TAKES_ARG | TOO_HARD}, // Clang
{"-gtoggle", TOO_HARD},
{"-idirafter", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
{"-iframework", AFFECTS_CPP | TAKES_ARG | TAKES_CONCAT_ARG | TAKES_PATH},
// Copyright (C) 2002 Andrew Tridgell
-// Copyright (C) 2011-2020 Joel Rosdahl and other contributors
+// Copyright (C) 2011-2021 Joel Rosdahl and other contributors
//
// See doc/AUTHORS.adoc for a complete list of contributors.
//
#include "fmtmacros.hpp"
#ifdef _WIN32
+# include "Finalizer.hpp"
# include "Win32Util.hpp"
#endif
using nonstd::string_view;
#ifdef _WIN32
+static int win32execute(const char* path,
+ const char* const* argv,
+ int doreturn,
+ int fd_stdout,
+ int fd_stderr,
+ const std::string& temp_dir);
+
int
-execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* /*pid*/)
+execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err)
+{
+ return win32execute(argv[0],
+ argv,
+ 1,
+ fd_out.release(),
+ fd_err.release(),
+ ctx.config.temporary_dir());
+}
+
+void
+execute_noreturn(const char* const* argv, const std::string& temp_dir)
{
- return win32execute(argv[0], argv, 1, fd_out.release(), fd_err.release());
+ win32execute(argv[0], argv, 0, -1, -1, temp_dir);
}
std::string
const char* const* argv,
int doreturn,
int fd_stdout,
- int fd_stderr)
+ int fd_stderr,
+ const std::string& temp_dir)
{
PROCESS_INFORMATION pi;
memset(&pi, 0x00, sizeof(pi));
std::string args = Win32Util::argv_to_string(argv, sh);
std::string full_path = Win32Util::add_exe_suffix(path);
std::string tmp_file_path;
+
+ Finalizer tmp_file_remover([&tmp_file_path] {
+ if (!tmp_file_path.empty()) {
+ Util::unlink_tmp(tmp_file_path);
+ }
+ });
+
if (args.length() > 8192) {
- TemporaryFile tmp_file(path);
+ TemporaryFile tmp_file(FMT("{}/cmd_args", temp_dir));
+ args = Win32Util::argv_to_string(argv + 1, sh, true);
Util::write_fd(*tmp_file.fd, args.data(), args.length());
- args = FMT("\"@{}\"", tmp_file.path);
+ args = FMT("{} @{}", full_path, tmp_file.path);
tmp_file_path = tmp_file.path;
+ LOG("Arguments from {}", tmp_file.path);
}
BOOL ret = CreateProcess(full_path.c_str(),
const_cast<char*>(args.c_str()),
nullptr,
&si,
&pi);
- if (!tmp_file_path.empty()) {
- Util::unlink_tmp(tmp_file_path);
- }
if (fd_stdout != -1) {
close(fd_stdout);
close(fd_stderr);
// Execute a compiler backend, capturing all output to the given paths the full
// path to the compiler to run is in argv[0].
int
-execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
+execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err)
{
LOG("Executing {}", Util::format_argv_for_logging(argv));
{
SignalHandlerBlocker signal_handler_blocker;
- *pid = fork();
+ ctx.compiler_pid = fork();
}
- if (*pid == -1) {
+ if (ctx.compiler_pid == -1) {
throw Fatal("Failed to fork: {}", strerror(errno));
}
- if (*pid == 0) {
+ if (ctx.compiler_pid == 0) {
// Child.
dup2(*fd_out, STDOUT_FILENO);
fd_out.close();
int status;
int result;
- while ((result = waitpid(*pid, &status, 0)) != *pid) {
+ while ((result = waitpid(ctx.compiler_pid, &status, 0)) != ctx.compiler_pid) {
if (result == -1 && errno == EINTR) {
continue;
}
{
SignalHandlerBlocker signal_handler_blocker;
- *pid = 0;
+ ctx.compiler_pid = 0;
}
if (WEXITSTATUS(status) == 0 && WIFSIGNALED(status)) {
return WEXITSTATUS(status);
}
+
+void
+execute_noreturn(const char* const* argv, const std::string& /*temp_dir*/)
+{
+ execv(argv[0], const_cast<char* const*>(argv));
+}
#endif
std::string
class Context;
-int execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid);
+int execute(Context& ctx, const char* const* argv, Fd&& fd_out, Fd&& fd_err);
+
+void execute_noreturn(const char* const* argv, const std::string& temp_dir);
// Find an executable named `name` in `$PATH`. Exclude any executables that are
// links to `exclude_name`.
#ifdef _WIN32
std::string win32getshell(const std::string& path);
-int win32execute(const char* path,
- const char* const* argv,
- int doreturn,
- int fd_stdout,
- int fd_stderr);
#endif
// function is available in libc.a. This extern define ensures that it is
// usable within the ccache code base.
#ifdef _AIX
-extern int usleep(useconds_t);
+extern "C" int usleep(useconds_t);
#endif
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
const mode_t S_IWUSR = mode_t(_S_IWRITE);
# endif
-// From https://stackoverflow.com/a/62371749/262458
-# define _CRT_INTERNAL_NONSTDC_NAMES 1
-# include <sys/stat.h>
-# if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG)
+# ifndef S_IFIFO
+# define S_IFIFO 0x1000
+# endif
+
+# ifndef S_IFBLK
+# define S_IFBLK 0x6000
+# endif
+
+# ifndef S_IFLNK
+# define S_IFLNK 0xA000
+# endif
+
+# ifndef S_ISREG
# define S_ISREG(m) (((m)&S_IFMT) == S_IFREG)
# endif
-# if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
+# ifndef S_ISDIR
# define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR)
# endif
+# ifndef S_ISFIFO
+# define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO)
+# endif
+# ifndef S_ISCHR
+# define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR)
+# endif
+# ifndef S_ISLNK
+# define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK)
+# endif
+# ifndef S_ISBLK
+# define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK)
+# endif
# include <direct.h>
# include <io.h>
# define NOMINMAX 1
# include <windows.h>
# define mkdir(a, b) _mkdir(a)
-# define execv(a, b) win32execute(a, b, 0, -1, -1)
+# define execv(a, b) \
+ do_not_call_execv_on_windows // to protect against incidental use of MinGW
+ // execv
# define strncasecmp _strnicmp
# define strcasecmp _stricmp
//
// doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD
//
-// Copyright (c) 2016-2020 Viktor Kirilov
+// Copyright (c) 2016-2021 Viktor Kirilov
//
// Distributed under the MIT Software License
// See accompanying file LICENSE.txt or copy at
#define DOCTEST_VERSION_MAJOR 2
#define DOCTEST_VERSION_MINOR 4
-#define DOCTEST_VERSION_PATCH 4
-#define DOCTEST_VERSION_STR "2.4.4"
+#define DOCTEST_VERSION_PATCH 6
+#define DOCTEST_VERSION_STR "2.4.6"
#define DOCTEST_VERSION \
(DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH)
#define DOCTEST_GLOBAL_NO_WARNINGS(var) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \
- static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp)
+ static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp)
#define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
#ifdef DOCTEST_PLATFORM_LINUX
#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64))
// Break at the location of the failing check if possible
-#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler)
#else
#include <signal.h>
#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP)
#endif
#elif defined(DOCTEST_PLATFORM_MAC)
#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386)
-#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler)
#else
-#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler)
#endif
#elif DOCTEST_MSVC
#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
struct DOCTEST_INTERFACE TestCaseData
{
- String m_file; // the file in which the test was registered
+ String m_file; // the file in which the test was registered (using String - see #350)
unsigned m_line; // the line where the test was registered
const char* m_name; // name of the test case
const char* m_test_suite; // the test suite in which the test was added
const char* m_description;
bool m_skip;
+ bool m_no_breaks;
+ bool m_no_output;
bool m_may_fail;
bool m_should_fail;
int m_expected_failures;
virtual void stringify(std::ostream*) const = 0;
};
+namespace detail {
+ struct DOCTEST_INTERFACE TestCase;
+} // namespace detail
+
struct ContextOptions //!OCLINT too many fields
{
std::ostream* cout; // stdout stream - std::cout by default
std::ostream* cerr; // stderr stream - std::cerr by default
String binary_name; // the test binary name
+ const detail::TestCase* currentTest = nullptr;
+
// == parameters from the command line
String out; // output filename
String order_by; // how tests should be ordered
template<class T> struct remove_reference<T&> { typedef T type; };
template<class T> struct remove_reference<T&&> { typedef T type; };
+ template<typename T, typename U = T&&> U declval(int);
+
+ template<typename T> T declval(long);
+
+ template<typename T> auto declval() DOCTEST_NOEXCEPT -> decltype(declval<T>(0)) ;
+
+ template<class T> struct is_lvalue_reference { const static bool value=false; };
+ template<class T> struct is_lvalue_reference<T&> { const static bool value=true; };
+
+ template <class T>
+ inline T&& forward(typename remove_reference<T>::type& t) DOCTEST_NOEXCEPT
+ {
+ return static_cast<T&&>(t);
+ }
+
+ template <class T>
+ inline T&& forward(typename remove_reference<T>::type&& t) DOCTEST_NOEXCEPT
+ {
+ static_assert(!is_lvalue_reference<T>::value,
+ "Can not forward an rvalue as an lvalue.");
+ return static_cast<T&&>(t);
+ }
+
template<class T> struct remove_const { typedef T type; };
template<class T> struct remove_const<const T> { typedef T type; };
#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS
return toString(lhs) + op + toString(rhs);
}
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison")
+#endif
+
+// This will check if there is any way it could find a operator like member or friend and uses it.
+// If not it doesn't find the operator or if the operator at global scope is defined after
+// this template, the template won't be instantiated due to SFINAE. Once the template is not
+// instantiated it can look for global operator using normal conversions.
+#define SFINAE_OP(ret,op) decltype(doctest::detail::declval<L>() op doctest::detail::declval<R>(),static_cast<ret>(0))
+
#define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \
template <typename R> \
- DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \
- bool res = op_macro(lhs, rhs); \
+ DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \
+ bool res = op_macro(doctest::detail::forward<L>(lhs), doctest::detail::forward<R>(rhs)); \
if(m_at & assertType::is_false) \
res = !res; \
if(!res || doctest::getContextOptions()->success) \
L lhs;
assertType::Enum m_at;
- explicit Expression_lhs(L in, assertType::Enum at)
- : lhs(in)
+ explicit Expression_lhs(L&& in, assertType::Enum at)
+ : lhs(doctest::detail::forward<L>(in))
, m_at(at) {}
DOCTEST_NOINLINE operator Result() {
- bool res = !!lhs;
+// this is needed only foc MSVC 2015:
+// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool
+ bool res = static_cast<bool>(lhs);
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional
res = !res;
return Result(res);
}
+ /* This is required for user-defined conversions from Expression_lhs to L */
+ //operator L() const { return lhs; }
+ operator L() const { return lhs; }
+
// clang-format off
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional
DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional
#endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION
+#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0)
+DOCTEST_CLANG_SUPPRESS_WARNING_POP
+#endif
+
struct DOCTEST_INTERFACE ExpressionDecomposer
{
assertType::Enum m_at;
// https://github.com/catchorg/Catch2/issues/870
// https://github.com/catchorg/Catch2/issues/565
template <typename L>
- Expression_lhs<const DOCTEST_REF_WRAP(L)> operator<<(const DOCTEST_REF_WRAP(L) operand) {
- return Expression_lhs<const DOCTEST_REF_WRAP(L)>(operand, m_at);
+ Expression_lhs<L> operator<<(L &&operand) {
+ return Expression_lhs<L>(doctest::detail::forward<L>(operand), m_at);
}
};
const char* m_test_suite;
const char* m_description;
bool m_skip;
+ bool m_no_breaks;
+ bool m_no_output;
bool m_may_fail;
bool m_should_fail;
int m_expected_failures;
template <typename L> class ContextScope : public ContextScopeBase
{
- const L &lambda_;
+ const L lambda_;
public:
explicit ContextScope(const L &lambda) : lambda_(lambda) {}
DOCTEST_DEFINE_DECORATOR(test_suite, const char*, "");
DOCTEST_DEFINE_DECORATOR(description, const char*, "");
DOCTEST_DEFINE_DECORATOR(skip, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true);
+DOCTEST_DEFINE_DECORATOR(no_output, bool, true);
DOCTEST_DEFINE_DECORATOR(timeout, double, 0);
DOCTEST_DEFINE_DECORATOR(may_fail, bool, true);
DOCTEST_DEFINE_DECORATOR(should_fail, bool, true);
static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \
DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \
DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \
- static doctest::detail::TestSuite data; \
+ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \
+ static doctest::detail::TestSuite data{}; \
static bool inited = false; \
DOCTEST_MSVC_SUPPRESS_WARNING_POP \
DOCTEST_CLANG_SUPPRESS_WARNING_POP \
+ DOCTEST_GCC_SUPPRESS_WARNING_POP \
if(!inited) { \
data* decorators; \
inited = true; \
// for logging
#define DOCTEST_INFO(...) \
DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \
- DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), __VA_ARGS__)
+ __VA_ARGS__)
-#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, ...) \
- DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \
- auto lambda_name = [&](std::ostream* s_name) { \
+#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \
+ auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \
+ [&](std::ostream* s_name) { \
doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \
mb_name.m_stream = s_name; \
mb_name * __VA_ARGS__; \
- }; \
- DOCTEST_MSVC_SUPPRESS_WARNING_POP \
- auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name)
+ })
#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x)
#define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE
#define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
-#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
+#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__)
// clang-format on
// BDD style macros
// == SHORT VERSIONS OF THE MACROS
#if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES)
-#define TEST_CASE DOCTEST_TEST_CASE
-#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS
-#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE
-#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING
-#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE
-#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE
-#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE
-#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY
-#define SUBCASE DOCTEST_SUBCASE
-#define TEST_SUITE DOCTEST_TEST_SUITE
-#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN
+#define TEST_CASE(name) DOCTEST_TEST_CASE(name)
+#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name)
+#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name)
+#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__)
+#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id)
+#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__)
+#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__)
+#define SUBCASE(name) DOCTEST_SUBCASE(name)
+#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators)
+#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name)
#define TEST_SUITE_END DOCTEST_TEST_SUITE_END
-#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR
-#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER
-#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER
-#define INFO DOCTEST_INFO
-#define CAPTURE DOCTEST_CAPTURE
-#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT
-#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT
-#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT
-#define MESSAGE DOCTEST_MESSAGE
-#define FAIL_CHECK DOCTEST_FAIL_CHECK
-#define FAIL DOCTEST_FAIL
-#define TO_LVALUE DOCTEST_TO_LVALUE
-
-#define WARN DOCTEST_WARN
-#define WARN_FALSE DOCTEST_WARN_FALSE
-#define WARN_THROWS DOCTEST_WARN_THROWS
-#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS
-#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH
-#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS
-#define WARN_NOTHROW DOCTEST_WARN_NOTHROW
-#define CHECK DOCTEST_CHECK
-#define CHECK_FALSE DOCTEST_CHECK_FALSE
-#define CHECK_THROWS DOCTEST_CHECK_THROWS
-#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS
-#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH
-#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS
-#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW
-#define REQUIRE DOCTEST_REQUIRE
-#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE
-#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS
-#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS
-#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH
-#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS
-#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW
-
-#define WARN_MESSAGE DOCTEST_WARN_MESSAGE
-#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE
-#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE
-#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE
-#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE
-#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE
-#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE
-#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE
-#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE
-#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE
-#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE
-#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE
-#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE
-#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE
-#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE
-#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE
-#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE
-#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE
-#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE
-#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE
-#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE
-
-#define SCENARIO DOCTEST_SCENARIO
-#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS
-#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE
-#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE
-#define GIVEN DOCTEST_GIVEN
-#define WHEN DOCTEST_WHEN
-#define AND_WHEN DOCTEST_AND_WHEN
-#define THEN DOCTEST_THEN
-#define AND_THEN DOCTEST_AND_THEN
-
-#define WARN_EQ DOCTEST_WARN_EQ
-#define CHECK_EQ DOCTEST_CHECK_EQ
-#define REQUIRE_EQ DOCTEST_REQUIRE_EQ
-#define WARN_NE DOCTEST_WARN_NE
-#define CHECK_NE DOCTEST_CHECK_NE
-#define REQUIRE_NE DOCTEST_REQUIRE_NE
-#define WARN_GT DOCTEST_WARN_GT
-#define CHECK_GT DOCTEST_CHECK_GT
-#define REQUIRE_GT DOCTEST_REQUIRE_GT
-#define WARN_LT DOCTEST_WARN_LT
-#define CHECK_LT DOCTEST_CHECK_LT
-#define REQUIRE_LT DOCTEST_REQUIRE_LT
-#define WARN_GE DOCTEST_WARN_GE
-#define CHECK_GE DOCTEST_CHECK_GE
-#define REQUIRE_GE DOCTEST_REQUIRE_GE
-#define WARN_LE DOCTEST_WARN_LE
-#define CHECK_LE DOCTEST_CHECK_LE
-#define REQUIRE_LE DOCTEST_REQUIRE_LE
-#define WARN_UNARY DOCTEST_WARN_UNARY
-#define CHECK_UNARY DOCTEST_CHECK_UNARY
-#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY
-#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE
-#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE
-#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE
+#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature)
+#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter)
+#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter)
+#define INFO(...) DOCTEST_INFO(__VA_ARGS__)
+#define CAPTURE(x) DOCTEST_CAPTURE(x)
+#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__)
+#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__)
+#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__)
+#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__)
+#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__)
+#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__)
+
+#define WARN(...) DOCTEST_WARN(__VA_ARGS__)
+#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__)
+#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__)
+#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__)
+#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__)
+#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__)
+#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__)
+#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__)
+#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__)
+#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__)
+#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__)
+#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__)
+#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__)
+
+#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__)
+#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__)
+#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__)
+#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__)
+#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__)
+#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__)
+
+#define SCENARIO(name) DOCTEST_SCENARIO(name)
+#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name)
+#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__)
+#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id)
+#define GIVEN(name) DOCTEST_GIVEN(name)
+#define WHEN(name) DOCTEST_WHEN(name)
+#define AND_WHEN(name) DOCTEST_AND_WHEN(name)
+#define THEN(name) DOCTEST_THEN(name)
+#define AND_THEN(name) DOCTEST_AND_THEN(name)
+
+#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__)
+#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__)
+#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__)
+#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__)
+#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__)
+#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__)
+#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__)
+#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__)
+#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__)
+#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__)
+#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__)
+#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__)
+#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__)
+#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__)
+#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__)
+#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__)
+#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__)
+#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__)
+#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__)
+#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__)
+#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__)
+#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
// KEPT FOR BACKWARDS COMPATIBILITY
-#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ
-#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ
-#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ
-#define FAST_WARN_NE DOCTEST_FAST_WARN_NE
-#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE
-#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE
-#define FAST_WARN_GT DOCTEST_FAST_WARN_GT
-#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT
-#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT
-#define FAST_WARN_LT DOCTEST_FAST_WARN_LT
-#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT
-#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT
-#define FAST_WARN_GE DOCTEST_FAST_WARN_GE
-#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE
-#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE
-#define FAST_WARN_LE DOCTEST_FAST_WARN_LE
-#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE
-#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE
-
-#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY
-#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY
-#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY
-#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE
-#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE
-#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE
-
-#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE
+#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__)
+#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__)
+#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__)
+#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__)
+#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__)
+#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__)
+#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__)
+#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__)
+#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__)
+#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__)
+#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__)
+#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__)
+#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__)
+#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__)
+#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__)
+#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__)
+#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__)
+#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__)
+
+#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__)
+#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__)
+#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__)
+#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__)
+#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__)
+
+#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__)
#endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function")
+DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path")
DOCTEST_GCC_SUPPRESS_WARNING_PUSH
DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas")
#ifdef __AFXDLL
#include <AfxWin.h>
#else
-#if defined(__MINGW32__) || defined(__MINGW64__)
#include <windows.h>
-#else // MINGW
-#include <Windows.h>
-#endif // MINGW
#endif
#include <io.h>
#define DOCTEST_THREAD_LOCAL thread_local
#endif
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES
+#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32
+#endif
+
+#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE
+#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64
+#endif
+
#ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS
#define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX
#else
#define DOCTEST_OPTIONS_PREFIX_DISPLAY ""
#endif
+#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
+#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+#endif
+
namespace doctest {
bool is_running_in_test = false;
ticks_t m_ticks = 0;
};
+#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+ template <typename T>
+ using AtomicOrMultiLaneAtomic = std::atomic<T>;
+#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+ // Provides a multilane implementation of an atomic variable that supports add, sub, load,
+ // store. Instead of using a single atomic variable, this splits up into multiple ones,
+ // each sitting on a separate cache line. The goal is to provide a speedup when most
+ // operations are modifying. It achieves this with two properties:
+ //
+ // * Multiple atomics are used, so chance of congestion from the same atomic is reduced.
+ // * Each atomic sits on a separate cache line, so false sharing is reduced.
+ //
+ // The disadvantage is that there is a small overhead due to the use of TLS, and load/store
+ // is slower because all atomics have to be accessed.
+ template <typename T>
+ class MultiLaneAtomic
+ {
+ struct CacheLineAlignedAtomic
+ {
+ std::atomic<T> atomic{};
+ char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic<T>)];
+ };
+ CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES];
+
+ static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE,
+ "guarantee one atomic takes exactly one cache line");
+
+ public:
+ T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; }
+
+ T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); }
+
+ T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ return myAtomic().fetch_add(arg, order);
+ }
+
+ T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ return myAtomic().fetch_sub(arg, order);
+ }
+
+ operator T() const DOCTEST_NOEXCEPT { return load(); }
+
+ T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT {
+ auto result = T();
+ for(auto const& c : m_atomics) {
+ result += c.atomic.load(order);
+ }
+ return result;
+ }
+
+ T operator=(T desired) DOCTEST_NOEXCEPT {
+ store(desired);
+ return desired;
+ }
+
+ void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT {
+ // first value becomes desired", all others become 0.
+ for(auto& c : m_atomics) {
+ c.atomic.store(desired, order);
+ desired = {};
+ }
+ }
+
+ private:
+ // Each thread has a different atomic that it operates on. If more than NumLanes threads
+ // use this, some will use the same atomic. So performance will degrate a bit, but still
+ // everything will work.
+ //
+ // The logic here is a bit tricky. The call should be as fast as possible, so that there
+ // is minimal to no overhead in determining the correct atomic for the current thread.
+ //
+ // 1. A global static counter laneCounter counts continuously up.
+ // 2. Each successive thread will use modulo operation of that counter so it gets an atomic
+ // assigned in a round-robin fashion.
+ // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with
+ // little overhead.
+ std::atomic<T>& myAtomic() DOCTEST_NOEXCEPT {
+ static std::atomic<size_t> laneCounter;
+ DOCTEST_THREAD_LOCAL size_t tlsLaneIdx =
+ laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES;
+
+ return m_atomics[tlsLaneIdx].atomic;
+ }
+ };
+
+ template <typename T>
+ using AtomicOrMultiLaneAtomic = MultiLaneAtomic<T>;
+#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS
+
// this holds both parameters from the command line and runtime data for tests
struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats
{
- std::atomic<int> numAssertsCurrentTest_atomic;
- std::atomic<int> numAssertsFailedCurrentTest_atomic;
+ AtomicOrMultiLaneAtomic<int> numAssertsCurrentTest_atomic;
+ AtomicOrMultiLaneAtomic<int> numAssertsFailedCurrentTest_atomic;
std::vector<std::vector<String>> filters = decltype(filters)(9); // 9 different filters
std::vector<IReporter*> reporters_currently_used;
- const TestCase* currentTest = nullptr;
-
assert_handler ah = nullptr;
Timer timer;
String::String(const char* in, unsigned in_size) {
using namespace std;
if(in_size <= last) {
- memcpy(buf, in, in_size + 1);
+ memcpy(buf, in, in_size);
+ buf[in_size] = '\0';
setLast(last - in_size);
} else {
setOnHeap();
data.size = in_size;
data.capacity = data.size + 1;
data.ptr = new char[data.capacity];
- memcpy(data.ptr, in, in_size + 1);
+ memcpy(data.ptr, in, in_size);
+ data.ptr[in_size] = '\0';
}
}
namespace doctest_detail_test_suite_ns {
// holds the current test suite
doctest::detail::TestSuite& getCurrentTestSuite() {
- static doctest::detail::TestSuite data;
+ static doctest::detail::TestSuite data{};
return data;
}
} // namespace doctest_detail_test_suite_ns
Subcase::Subcase(const String& name, const char* file, int line)
: m_signature({name, file, line}) {
- ContextState* s = g_cs;
+ auto* s = g_cs;
// check subcase filters
if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) {
g_cs->subcasesPassed.insert(g_cs->subcasesStack);
g_cs->subcasesStack.pop_back();
-#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
if(std::uncaught_exceptions() > 0
#else
if(std::uncaught_exception()
// clear state
m_description = nullptr;
m_skip = false;
+ m_no_breaks = false;
+ m_no_output = false;
m_may_fail = false;
m_should_fail = false;
m_expected_failures = 0;
m_test_suite = test_suite.m_test_suite;
m_description = test_suite.m_description;
m_skip = test_suite.m_skip;
+ m_no_breaks = test_suite.m_no_breaks;
+ m_no_output = test_suite.m_no_output;
m_may_fail = test_suite.m_may_fail;
m_should_fail = test_suite.m_should_fail;
m_expected_failures = test_suite.m_expected_failures;
// this will be used only to differentiate between test cases - not relevant for sorting
if(m_line != other.m_line)
return m_line < other.m_line;
- const int file_cmp = m_file.compare(other.m_file);
- if(file_cmp != 0)
- return file_cmp < 0;
const int name_cmp = strcmp(m_name, other.m_name);
if(name_cmp != 0)
return name_cmp < 0;
+ const int file_cmp = m_file.compare(other.m_file);
+ if(file_cmp != 0)
+ return file_cmp < 0;
return m_template_id < other.m_template_id;
}
+
+ // all the registered tests
+ std::set<TestCase>& getRegisteredTests() {
+ static std::set<TestCase> data;
+ return data;
+ }
} // namespace detail
namespace {
using namespace detail;
return suiteOrderComparator(lhs, rhs);
}
- // all the registered tests
- std::set<TestCase>& getRegisteredTests() {
- static std::set<TestCase> data;
- return data;
- }
-
#ifdef DOCTEST_CONFIG_COLORS_WINDOWS
HANDLE g_stdoutHandle;
WORD g_origFgAttrs;
// ContextScope has been destroyed (base class destructors run after derived class destructors).
// Instead, ContextScope calls this method directly from its destructor.
void ContextScopeBase::destroy() {
-#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L
+#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200)
if(std::uncaught_exceptions() > 0) {
#else
if(std::uncaught_exception()) {
#if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH)
struct FatalConditionHandler
{
- void reset() {}
+ static void reset() {}
+ static void allocateAltStackMem() {}
+ static void freeAltStackMem() {}
};
#else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
std::exit(EXIT_FAILURE);
}
+ static void allocateAltStackMem() {}
+ static void freeAltStackMem() {}
+
FatalConditionHandler() {
isSet = true;
// 32k seems enough for doctest to handle stack overflow,
// - std::terminate is called FROM THE TEST RUNNER THREAD
// - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD
original_terminate_handler = std::get_terminate();
- std::set_terminate([]() noexcept {
+ std::set_terminate([]() DOCTEST_NOEXCEPT {
reportFatal("Terminate handler called");
if(isDebuggerActive() && !g_cs->no_breaks)
DOCTEST_BREAK_INTO_DEBUGGER();
// - std::terminate is called FROM A DIFFERENT THREAD
// - an exception is thrown from a destructor FROM A DIFFERENT THREAD
// - an uncaught exception is thrown FROM A DIFFERENT THREAD
- prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) noexcept {
+ prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT {
if(signal == SIGABRT) {
reportFatal("SIGABRT - Abort (abnormal termination) signal");
if(isDebuggerActive() && !g_cs->no_breaks)
SetErrorMode(prev_error_mode_1);
_set_error_mode(prev_error_mode_2);
_set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
- _CrtSetReportMode(_CRT_ASSERT, prev_report_mode);
- _CrtSetReportFile(_CRT_ASSERT, prev_report_file);
+ static_cast<void>(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode));
+ static_cast<void>(_CrtSetReportFile(_CRT_ASSERT, prev_report_file));
isSet = false;
}
}
static bool isSet;
static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)];
static stack_t oldSigStack;
- static char altStackMem[4 * SIGSTKSZ];
+ static size_t altStackSize;
+ static char* altStackMem;
static void handleSignal(int sig) {
const char* name = "<unknown signal>";
raise(sig);
}
+ static void allocateAltStackMem() {
+ altStackMem = new char[altStackSize];
+ }
+
+ static void freeAltStackMem() {
+ delete[] altStackMem;
+ }
+
FatalConditionHandler() {
isSet = true;
stack_t sigStack;
sigStack.ss_sp = altStackMem;
- sigStack.ss_size = sizeof(altStackMem);
+ sigStack.ss_size = altStackSize;
sigStack.ss_flags = 0;
sigaltstack(&sigStack, &oldSigStack);
struct sigaction sa = {};
}
};
- bool FatalConditionHandler::isSet = false;
+ bool FatalConditionHandler::isSet = false;
struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {};
- stack_t FatalConditionHandler::oldSigStack = {};
- char FatalConditionHandler::altStackMem[] = {};
+ stack_t FatalConditionHandler::oldSigStack = {};
+ size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ;
+ char* FatalConditionHandler::altStackMem = nullptr;
#endif // DOCTEST_PLATFORM_WINDOWS
#endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH
failed_out_of_a_testing_context(*this);
}
- return m_failed && isDebuggerActive() &&
- !getContextOptions()->no_breaks; // break into debugger
+ return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks &&
+ (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
}
void ResultBuilder::react() const {
addFailedAssert(m_severity);
}
- return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break
+ return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn &&
+ (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger
}
void MessageBuilder::react() {
<< Whitespace(sizePrefixDisplay*1) << "output filename\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by=<string> "
<< Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n";
- s << Whitespace(sizePrefixDisplay*3) << " <string> - by [file/suite/name/rand]\n";
+ s << Whitespace(sizePrefixDisplay*3) << " <string> - [file/suite/name/rand/none]\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed=<int> "
<< Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n";
s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first=<int> "
}
void test_case_end(const CurrentTestCaseStats& st) override {
+ if(tc->m_no_output)
+ return;
+
// log the preamble of the test case only if there is something
// else to print - something other than that an assert has failed
if(opt.duration ||
}
void test_case_exception(const TestCaseException& e) override {
+ if(tc->m_no_output)
+ return;
+
logTestStart();
file_line_to_stream(tc->m_file.c_str(), tc->m_line, " ");
}
void log_assert(const AssertData& rb) override {
- if(!rb.m_failed && !opt.success)
+ if((!rb.m_failed && !opt.success) || tc->m_no_output)
return;
std::lock_guard<std::mutex> lock(mutex);
}
void log_message(const MessageData& mb) override {
+ if(tc->m_no_output)
+ return;
+
std::lock_guard<std::mutex> lock(mutex);
logTestStart();
bool with_col = g_no_colors; \
g_no_colors = false; \
ConsoleReporter::func(arg); \
- DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \
- oss.str(""); \
+ if(oss.tellp() != std::streampos{}) { \
+ DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \
+ oss.str(""); \
+ } \
g_no_colors = with_col; \
}
#define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \
if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \
parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \
- p->var = !!intRes; \
+ p->var = static_cast<bool>(intRes); \
else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \
parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \
p->var = true; \
p->cout = &fstr;
}
+ FatalConditionHandler::allocateAltStackMem();
+
auto cleanup_and_return = [&]() {
+ FatalConditionHandler::freeAltStackMem();
+
if(fstr.is_open())
fstr.close();
first[i] = first[idxToSwap];
first[idxToSwap] = temp;
}
+ } else if(p->order_by.compare("none", true) == 0) {
+ // means no sorting - beneficial for death tests which call into the executable
+ // with a specific test case in mind - we don't want to slow down the startup times
}
}
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
try {
#endif // DOCTEST_CONFIG_NO_EXCEPTIONS
+// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method)
+DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable
FatalConditionHandler fatalConditionHandler; // Handle signals
// execute the test
tc.m_test();
fatalConditionHandler.reset();
+DOCTEST_MSVC_SUPPRESS_WARNING_POP
#ifndef DOCTEST_CONFIG_NO_EXCEPTIONS
} catch(const TestFailureException&) {
p->failure_flags |= TestCaseFailureReason::AssertFailure;
--- /dev/null
+#ifndef CCACHE_THIRD_PARTY_WIN32_WINERROR_TO_ERRNO_H_
+#define CCACHE_THIRD_PARTY_WIN32_WINERROR_TO_ERRNO_H_
+
+#include <errno.h>
+#include <winerror.h>
+
+// Based on
+// https://github.com/python/cpython/blob/cd8dcbc851fcc312722cdb5544c2f25cf46b3f8a/PC/errmap.h
+
+inline int
+winerror_to_errno(unsigned long winerror)
+{
+ // Unwrap FACILITY_WIN32 HRESULT errors.
+ if ((winerror & 0xFFFF0000) == 0x80070000) {
+ winerror &= 0x0000FFFF;
+ }
+
+ // Winsock error codes (10000-11999) are errno values.
+ if (winerror >= 10000 && winerror < 12000) {
+ switch (winerror) {
+ case WSAEINTR:
+ case WSAEBADF:
+ case WSAEACCES:
+ case WSAEFAULT:
+ case WSAEINVAL:
+ case WSAEMFILE:
+ // Winsock definitions of errno values. See WinSock2.h
+ return winerror - 10000;
+ default:
+ return winerror;
+ }
+ }
+
+ switch (winerror) {
+ case ERROR_FILE_NOT_FOUND: // 2
+ case ERROR_PATH_NOT_FOUND: // 3
+ case ERROR_INVALID_DRIVE: // 15
+ case ERROR_NO_MORE_FILES: // 18
+ case ERROR_BAD_NETPATH: // 53
+ case ERROR_BAD_NET_NAME: // 67
+ case ERROR_BAD_PATHNAME: // 161
+ case ERROR_FILENAME_EXCED_RANGE: // 206
+ return ENOENT;
+
+ case ERROR_BAD_ENVIRONMENT: // 10
+ return E2BIG;
+
+ case ERROR_BAD_FORMAT: // 11
+ case ERROR_INVALID_STARTING_CODESEG: // 188
+ case ERROR_INVALID_STACKSEG: // 189
+ case ERROR_INVALID_MODULETYPE: // 190
+ case ERROR_INVALID_EXE_SIGNATURE: // 191
+ case ERROR_EXE_MARKED_INVALID: // 192
+ case ERROR_BAD_EXE_FORMAT: // 193
+ case ERROR_ITERATED_DATA_EXCEEDS_64k: // 194
+ case ERROR_INVALID_MINALLOCSIZE: // 195
+ case ERROR_DYNLINK_FROM_INVALID_RING: // 196
+ case ERROR_IOPL_NOT_ENABLED: // 197
+ case ERROR_INVALID_SEGDPL: // 198
+ case ERROR_AUTODATASEG_EXCEEDS_64k: // 199
+ case ERROR_RING2SEG_MUST_BE_MOVABLE: // 200
+ case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: // 201
+ case ERROR_INFLOOP_IN_RELOC_CHAIN: // 202
+ return ENOEXEC;
+
+ case ERROR_INVALID_HANDLE: // 6
+ case ERROR_INVALID_TARGET_HANDLE: // 114
+ case ERROR_DIRECT_ACCESS_HANDLE: // 130
+ return EBADF;
+
+ case ERROR_WAIT_NO_CHILDREN: // 128
+ case ERROR_CHILD_NOT_COMPLETE: // 129
+ return ECHILD;
+
+ case ERROR_NO_PROC_SLOTS: // 89
+ case ERROR_MAX_THRDS_REACHED: // 164
+ case ERROR_NESTING_NOT_ALLOWED: // 215
+ return EAGAIN;
+
+ case ERROR_ARENA_TRASHED: // 7
+ case ERROR_NOT_ENOUGH_MEMORY: // 8
+ case ERROR_INVALID_BLOCK: // 9
+ case ERROR_NOT_ENOUGH_QUOTA: // 1816
+ return ENOMEM;
+
+ case ERROR_ACCESS_DENIED: // 5
+ case ERROR_CURRENT_DIRECTORY: // 16
+ case ERROR_WRITE_PROTECT: // 19
+ case ERROR_BAD_UNIT: // 20
+ case ERROR_NOT_READY: // 21
+ case ERROR_BAD_COMMAND: // 22
+ case ERROR_CRC: // 23
+ case ERROR_BAD_LENGTH: // 24
+ case ERROR_SEEK: // 25
+ case ERROR_NOT_DOS_DISK: // 26
+ case ERROR_SECTOR_NOT_FOUND: // 27
+ case ERROR_OUT_OF_PAPER: // 28
+ case ERROR_WRITE_FAULT: // 29
+ case ERROR_READ_FAULT: // 30
+ case ERROR_GEN_FAILURE: // 31
+ case ERROR_SHARING_VIOLATION: // 32
+ case ERROR_LOCK_VIOLATION: // 33
+ case ERROR_WRONG_DISK: // 34
+ case ERROR_SHARING_BUFFER_EXCEEDED: // 36
+ case ERROR_NETWORK_ACCESS_DENIED: // 65
+ case ERROR_CANNOT_MAKE: // 82
+ case ERROR_FAIL_I24: // 83
+ case ERROR_DRIVE_LOCKED: // 108
+ case ERROR_SEEK_ON_DEVICE: // 132
+ case ERROR_NOT_LOCKED: // 158
+ case ERROR_LOCK_FAILED: // 167
+ case 35: // 35 (undefined)
+ return EACCES;
+
+ case ERROR_FILE_EXISTS: // 80
+ case ERROR_ALREADY_EXISTS: // 183
+ return EEXIST;
+
+ case ERROR_NOT_SAME_DEVICE: // 17
+ return EXDEV;
+
+ case ERROR_DIRECTORY: // 267 (bpo-12802)
+ return ENOTDIR;
+
+ case ERROR_TOO_MANY_OPEN_FILES: // 4
+ return EMFILE;
+
+ case ERROR_DISK_FULL: // 112
+ return ENOSPC;
+
+ case ERROR_BROKEN_PIPE: // 109
+ case ERROR_NO_DATA: // 232 (bpo-13063)
+ return EPIPE;
+
+ case ERROR_DIR_NOT_EMPTY: // 145
+ return ENOTEMPTY;
+
+ case ERROR_NO_UNICODE_TRANSLATION: // 1113
+ return EILSEQ;
+
+ case ERROR_INVALID_FUNCTION: // 1
+ case ERROR_INVALID_ACCESS: // 12
+ case ERROR_INVALID_DATA: // 13
+ case ERROR_INVALID_PARAMETER: // 87
+ case ERROR_NEGATIVE_SEEK: // 131
+ default:
+ return EINVAL;
+ }
+}
+
+#endif
# A simple test suite for ccache.
#
# Copyright (C) 2002-2007 Andrew Tridgell
-# Copyright (C) 2009-2020 Joel Rosdahl and other contributors
+# Copyright (C) 2009-2021 Joel Rosdahl and other contributors
#
# See doc/AUTHORS.adoc for a complete list of contributors.
#
printf "$ansi_bold%s$ansi_reset\n" "$*"
}
-test_failed() {
+test_failed_internal() {
+ # Called from functions inside this file, so skip the first caller:
+ local line="$(caller 1)"
+ line=" (line ${line%% *})"
+
echo
red FAILED
echo
- echo "Test suite: $(bold $CURRENT_SUITE)"
+ echo "Test suite: $(bold $CURRENT_SUITE)$line"
echo "Test case: $(bold $CURRENT_TEST)"
echo "Failure reason: $(red "$1")"
echo
exit 1
}
+# Indirection so the line returned by `caller` is correct.
+test_failed() {
+ test_failed_internal "$@"
+}
+
find_compiler() {
local name=$1
perl -e '
done < <($CCACHE -s)
if [ "$expected_value" != "$value" ]; then
- test_failed "Expected \"$stat\" to be $expected_value, actual $value"
+ test_failed_internal "Expected \"$stat\" to be $expected_value, actual $value"
fi
}
expect_exists() {
if [ ! -e "$1" ]; then
- test_failed "Expected $1 to exist, but it's missing"
+ test_failed_internal "Expected $1 to exist, but it's missing"
fi
}
expect_missing() {
if [ -e "$1" ]; then
- test_failed "Expected $1 to be missing, but it exists"
+ test_failed_internal "Expected $1 to be missing, but it exists"
fi
}
expect_equal_content() {
if [ ! -e "$1" ]; then
- test_failed "expect_equal_content: $1 missing"
+ test_failed_internal "expect_equal_content: $1 missing"
fi
if [ ! -e "$2" ]; then
- test_failed "expect_equal_content: $2 missing"
+ test_failed_internal "expect_equal_content: $2 missing"
fi
if ! cmp -s "$1" "$2"; then
- test_failed "$1 and $2 differ"
+ test_failed_internal "$1 and $2 differ"
fi
}
expect_equal_text_content() {
if [ ! -e "$1" ]; then
- test_failed "expect_equal_text_content: $1 missing"
+ test_failed_internal "expect_equal_text_content: $1 missing"
fi
if [ ! -e "$2" ]; then
- test_failed "expect_equal_text_content: $2 missing"
+ test_failed_internal "expect_equal_text_content: $2 missing"
fi
if ! cmp -s "$1" "$2"; then
- test_failed "$1 and $2 differ: $(echo; diff -u "$1" "$2")"
+ test_failed_internal "$1 and $2 differ: $(echo; diff -u "$1" "$2")"
fi
}
expect_different_content() {
if [ ! -e "$1" ]; then
- test_failed "expect_different_content: $1 missing"
+ test_failed_internal "expect_different_content: $1 missing"
fi
if [ ! -e "$2" ]; then
- test_failed "expect_different_content: $2 missing"
+ test_failed_internal "expect_different_content: $2 missing"
fi
if cmp -s "$1" "$2"; then
- test_failed "$1 and $2 are identical"
+ test_failed_internal "$1 and $2 are identical"
fi
}
is_equal_object_files() {
if $HOST_OS_LINUX && $COMPILER_TYPE_CLANG; then
if ! command -v eu-elfcmp >/dev/null; then
- test_failed "Please install elfutils to get eu-elfcmp"
+ test_failed_internal "Please install elfutils to get eu-elfcmp"
fi
eu-elfcmp -q "$1" "$2"
elif $HOST_OS_FREEBSD && $COMPILER_TYPE_CLANG; then
expect_equal_object_files() {
is_equal_object_files "$1" "$2"
if [ $? -ne 0 ]; then
- test_failed "Objects differ: $1 != $2"
+ test_failed_internal "Objects differ: $1 != $2"
fi
}
local content="$2"
if [ ! -e "$file" ]; then
- test_failed "$file not found"
+ test_failed_internal "$file not found"
fi
if [ "$(cat $file)" != "$content" ]; then
- test_failed "Bad content of $file.\nExpected: $content\nActual: $(cat $file)"
+ test_failed_internal "Bad content of $file.\nExpected: $content\nActual: $(cat $file)"
fi
}
local string="$2"
if [ ! -e "$file" ]; then
- test_failed "$file not found"
+ test_failed_internal "$file not found"
fi
- if ! fgrep -q "$string" "$file"; then
- test_failed "File $file does not contain \"$string\"\nActual content: $(cat $file)"
+ if ! fgrep -q -- "$string" "$file"; then
+ test_failed_internal "File $file does not contain \"$string\"\nActual content: $(cat $file)"
fi
}
local string="$2"
if [ ! -e "$file" ]; then
- test_failed "$file not found"
+ test_failed_internal "$file not found"
fi
- if fgrep -q "$string" "$file"; then
- test_failed "File $file contains \"$string\"\nActual content: $(cat $file)"
+ if fgrep -q -- "$string" "$file"; then
+ test_failed_internal "File $file contains \"$string\"\nActual content: $(cat $file)"
fi
}
local string="$2"
if ! objdump_cmd "$file" | objdump_grep_cmd "$string"; then
- test_failed "File $file does not contain \"$string\""
+ test_failed_internal "File $file does not contain \"$string\""
fi
}
local string="$2"
if objdump_cmd "$file" | objdump_grep_cmd "$string"; then
- test_failed "File $file contains \"$string\""
+ test_failed_internal "File $file contains \"$string\""
fi
}
local dir=$3
local actual=`find $dir -type f -name "$pattern" | wc -l`
if [ $actual -ne $expected ]; then
- test_failed "Found $actual (expected $expected) $pattern files in $dir"
+ test_failed_internal "Found $actual (expected $expected) $pattern files in $dir"
fi
}
local newer_file=$1
local older_file=$2
if [ "$newer_file" -ot "$older_file" ]; then
- test_failed "$newer_file is older than $older_file"
+ test_failed_internal "$newer_file is older than $older_file"
fi
}
local expected_perm="$2"
local actual_perm=$(ls -ld "$path" | awk '{print substr($1, 1, 10)}')
if [ "$expected_perm" != "$actual_perm" ]; then
- test_failed "Expected permissions for $path to be $expected_perm, actual $actual_perm"
+ test_failed_internal "Expected permissions for $path to be $expected_perm, actual $actual_perm"
fi
}
expect_stat 'cache miss' 1
$REAL_COMPILER -c -o reference_test1.o test1.c -g
- expect_equal_object_files reference_test1.o reference_test1.o
+ expect_equal_object_files reference_test1.o test1.o
# -------------------------------------------------------------------------
TEST "Output option"
fi
done
+ # -------------------------------------------------------------------------
+ TEST "CCACHE_DEBUG with too hard option"
+
+ CCACHE_DEBUG=1 $CCACHE_COMPILE -c test1.c -save-temps
+ expect_stat 'unsupported compiler option' 1
+ expect_exists test1.o.ccache-log
+
# -------------------------------------------------------------------------
TEST "CCACHE_DISABLE"
expect_stat 'cache hit (preprocessed)' 2
expect_stat 'cache miss' 2
+ # -------------------------------------------------------------------------
+ TEST "-frecord-gcc-switches"
+
+ if $REAL_COMPILER -frecord-gcc-switches -c test1.c >&/dev/null; then
+ $CCACHE_COMPILE -frecord-gcc-switches -c test1.c
+ expect_stat 'cache hit (preprocessed)' 0
+ expect_stat 'cache miss' 1
+
+ $CCACHE_COMPILE -frecord-gcc-switches -c test1.c
+ expect_stat 'cache hit (preprocessed)' 1
+ expect_stat 'cache miss' 1
+
+ $CCACHE_COMPILE -frecord-gcc-switches -Wall -c test1.c
+ expect_stat 'cache hit (preprocessed)' 1
+ expect_stat 'cache miss' 2
+
+ $CCACHE_COMPILE -frecord-gcc-switches -Wall -c test1.c
+ expect_stat 'cache hit (preprocessed)' 2
+ expect_stat 'cache miss' 2
+ fi
+
# -------------------------------------------------------------------------
TEST "CCACHE_COMPILER"
# -------------------------------------------------------------------------
if ! $HOST_OS_WINDOWS; then
- TEST "UNCACHED_ERR_FD"
+ TEST "UNCACHED_ERR_FD set when executing preprocessor and compiler"
cat >compiler.sh <<'EOF'
#!/bin/sh
fi
fi
+if ! $HOST_OS_WINDOWS; then
+ TEST "UNCACHED_ERR_FD not set when falling back to the original command"
+ # -------------------------------------------------------------------------
+
+ $CCACHE bash -c 'echo $UNCACHED_ERR_FD' >uncached_err_fd.txt
+ expect_content uncached_err_fd.txt ""
+fi
+
# -------------------------------------------------------------------------
TEST "Invalid boolean environment configuration options"
if $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
fi
+ expect_stat 'unsupported compiler option' 1
# ---------------------------------------------------------------------
TEST "-fcolor-diagnostics not accepted for GCC for cached result"
if $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
fi
+ expect_stat 'unsupported compiler option' 1
+
+ # ---------------------------------------------------------------------
+ TEST "-fcolor-diagnostics passed to underlying compiler for unknown compiler type"
+
+ generate_code 1 test.c
+
+ if CCACHE_COMPILERTYPE=other $CCACHE_COMPILE -fcolor-diagnostics -c test.c >&/dev/null; then
+ test_failed "-fcolor-diagnostics unexpectedly accepted by GCC"
+ fi
+
+ # Verify that -fcolor-diagnostics was passed to the compiler for the
+ # unknown compiler case, i.e. ccache did not exit early with
+ # "unsupported compiler option".
+ expect_stat 'compile failed' 1
+ fi
+
+ if $COMPILER_TYPE_CLANG; then
+ # ---------------------------------------------------------------------
+ TEST "-fcolor-diagnostics works when passed to cc1 with -Xclang"
+
+ color_diagnostics_generate_code test1.c
+ $CCACHE_COMPILE -Xclang -fcolor-diagnostics -Wreturn-type -c -o test1.o test1.c 2>test1.stderr
+ color_diagnostics_expect_color test1.stderr
fi
while read -r case; do
// this program; if not, write to the Free Software Foundation, Inc., 51
// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+#include "../src/Finalizer.hpp"
#include "../src/Stat.hpp"
#include "../src/Util.hpp"
#include "TestUtil.hpp"
# include <unistd.h>
#endif
+#ifdef _WIN32
+# include <shlobj.h>
+#endif
+
using TestUtil::TestContext;
+namespace {
+
+bool
+running_under_wine()
+{
+#ifdef _WIN32
+ static bool is_wine =
+ GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "wine_get_version")
+ != nullptr;
+ return is_wine;
+#else
+ return false;
+#endif
+}
+
+bool
+symlinks_supported()
+{
+#ifdef _WIN32
+ // Windows only supports symlinks if the user has the required privilege (e.g.
+ // they're an admin) or if developer mode is enabled.
+
+ // See: https://stackoverflow.com/a/41232108/192102
+ const char* dev_mode_key =
+ "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock";
+ const char* dev_mode_value = "AllowDevelopmentWithoutDevLicense";
+
+ DWORD dev_mode_enabled = 0;
+ DWORD buf_size = sizeof(dev_mode_enabled);
+
+ return !running_under_wine()
+ && (IsUserAnAdmin()
+ || (RegGetValueA(HKEY_LOCAL_MACHINE,
+ dev_mode_key,
+ dev_mode_value,
+ RRF_RT_DWORD,
+ nullptr,
+ &dev_mode_enabled,
+ &buf_size)
+ == ERROR_SUCCESS
+ && dev_mode_enabled));
+#else
+ return true;
+#endif
+}
+
+#ifdef _WIN32
+bool
+win32_is_junction(const std::string& path)
+{
+ HANDLE handle =
+ CreateFileA(path.c_str(),
+ FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ nullptr);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+ FILE_ATTRIBUTE_TAG_INFO reparse_info = {};
+ bool is_junction =
+ (GetFileType(handle) == FILE_TYPE_DISK)
+ && GetFileInformationByHandleEx(
+ handle, FileAttributeTagInfo, &reparse_info, sizeof(reparse_info))
+ && (reparse_info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
+ && (reparse_info.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT);
+ CloseHandle(handle);
+ return is_junction;
+}
+
+bool
+win32_get_file_info(const std::string& path, BY_HANDLE_FILE_INFORMATION* info)
+{
+ HANDLE handle =
+ CreateFileA(path.c_str(),
+ FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ nullptr);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+ BOOL ret = GetFileInformationByHandle(handle, info);
+ CloseHandle(handle);
+ return ret;
+}
+
+struct timespec
+win32_filetime_to_timespec(FILETIME ft)
+{
+ static const int64_t SECS_BETWEEN_EPOCHS = 11644473600;
+ uint64_t v =
+ (static_cast<uint64_t>(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+ struct timespec ts = {};
+ ts.tv_sec = (v / 10000000) - SECS_BETWEEN_EPOCHS;
+ ts.tv_nsec = (v % 10000000) * 100;
+ return ts;
+}
+#endif
+
+} // namespace
+
TEST_SUITE_BEGIN("Stat");
TEST_CASE("Default constructor")
CHECK(!stat.is_regular());
CHECK(!stat.is_symlink());
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
CHECK(stat.ctim().tv_sec == 0);
CHECK(stat.ctim().tv_nsec == 0);
-#endif
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
CHECK(stat.mtim().tv_sec == 0);
CHECK(stat.mtim().tv_nsec == 0);
+
+#ifdef _WIN32
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
#endif
}
auto b_stat = Stat::stat("b");
CHECK(a_stat.same_inode_as(a_stat));
-#ifdef _WIN32 // no i-node concept
- (void)b_stat;
-#else
CHECK(!a_stat.same_inode_as(b_stat));
-#endif
Util::write_file("a", "change size");
auto new_a_stat = Stat::stat("a");
CHECK(!stat.is_regular());
CHECK(!stat.is_symlink());
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
CHECK(stat.ctim().tv_sec == 0);
CHECK(stat.ctim().tv_nsec == 0);
-#endif
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
CHECK(stat.mtim().tv_sec == 0);
CHECK(stat.mtim().tv_nsec == 0);
+
+#ifdef _WIN32
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
#endif
}
Util::write_file("file", "1234567");
auto stat = Stat::stat("file");
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(stat.size() == 7);
+
+#ifdef _WIN32
+ BY_HANDLE_FILE_INFORMATION info = {};
+ CHECK(win32_get_file_info("file", &info));
+
+ CHECK(stat.device() == info.dwVolumeSerialNumber);
+ CHECK((stat.inode() >> 32) == info.nFileIndexHigh);
+ CHECK((stat.inode() & ((1ULL << 32) - 1)) == info.nFileIndexLow);
+ CHECK(S_ISREG(stat.mode()));
+ CHECK((stat.mode() & ~S_IFMT) == 0666);
+
+ struct timespec creation_time =
+ win32_filetime_to_timespec(info.ftCreationTime);
+ struct timespec last_write_time =
+ win32_filetime_to_timespec(info.ftLastWriteTime);
+
+ CHECK(stat.ctime() == creation_time.tv_sec);
+ CHECK(stat.mtime() == last_write_time.tv_sec);
+
+ CHECK(stat.ctim().tv_sec == creation_time.tv_sec);
+ CHECK(stat.ctim().tv_nsec == creation_time.tv_nsec);
+ CHECK(stat.mtim().tv_sec == last_write_time.tv_sec);
+ CHECK(stat.mtim().tv_nsec == last_write_time.tv_nsec);
+
+ CHECK(stat.size_on_disk() == ((stat.size() + 1023) & ~1023));
+ CHECK(stat.file_attributes() == info.dwFileAttributes);
+ CHECK(stat.reparse_tag() == 0);
+
+#else
struct stat st;
CHECK(::stat("file", &st) == 0);
- CHECK(stat);
- CHECK(stat.error_number() == 0);
CHECK(stat.device() == st.st_dev);
CHECK(stat.inode() == st.st_ino);
CHECK(stat.mode() == st.st_mode);
CHECK(stat.ctime() == st.st_ctime);
CHECK(stat.mtime() == st.st_mtime);
- CHECK(stat.size() == st.st_size);
-#ifdef _WIN32
- CHECK(stat.size_on_disk() == ((stat.size() + 1023) & ~1023));
-#else
CHECK(stat.size_on_disk() == st.st_blocks * 512);
-#endif
- CHECK(!stat.is_directory());
- CHECK(stat.is_regular());
- CHECK(!stat.is_symlink());
-#ifdef HAVE_STRUCT_STAT_ST_CTIM
+# ifdef HAVE_STRUCT_STAT_ST_CTIM
CHECK(stat.ctim().tv_sec == st.st_ctim.tv_sec);
CHECK(stat.ctim().tv_nsec == st.st_ctim.tv_nsec);
-#endif
+# else
+ CHECK(stat.ctim().tv_sec == st.st_ctime);
+ CHECK(stat.ctim().tv_nsec == 0);
+# endif
-#ifdef HAVE_STRUCT_STAT_ST_MTIM
+# ifdef HAVE_STRUCT_STAT_ST_MTIM
CHECK(stat.mtim().tv_sec == st.st_mtim.tv_sec);
CHECK(stat.mtim().tv_nsec == st.st_mtim.tv_nsec);
+# else
+ CHECK(stat.mtim().tv_sec == st.st_mtime);
+ CHECK(stat.mtim().tv_nsec == 0);
+# endif
#endif
}
CHECK(stat.is_directory());
CHECK(!stat.is_regular());
CHECK(!stat.is_symlink());
+ CHECK(S_ISDIR(stat.mode()));
+#ifdef _WIN32
+ CHECK((stat.mode() & ~S_IFMT) == 0777);
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+#endif
}
-#ifndef _WIN32
-TEST_CASE("Symlinks")
+TEST_CASE("Symlinks" * doctest::skip(!symlinks_supported()))
{
TestContext test_context;
Util::write_file("file", "1234567");
+#ifdef _WIN32
+ REQUIRE(CreateSymbolicLinkA(
+ "symlink", "file", 0x2 /*SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE*/));
+#else
+ REQUIRE(symlink("file", "symlink") == 0);
+#endif
+
SUBCASE("file lstat")
{
auto stat = Stat::lstat("file", Stat::OnError::ignore);
CHECK(stat);
+ CHECK(stat.error_number() == 0);
CHECK(!stat.is_directory());
CHECK(stat.is_regular());
CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
CHECK(stat.size() == 7);
+#ifdef _WIN32
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+#endif
}
SUBCASE("file stat")
{
auto stat = Stat::stat("file", Stat::OnError::ignore);
CHECK(stat);
+ CHECK(stat.error_number() == 0);
CHECK(!stat.is_directory());
CHECK(stat.is_regular());
CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
CHECK(stat.size() == 7);
+#ifdef _WIN32
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+#endif
}
SUBCASE("symlink lstat")
{
- REQUIRE(symlink("file", "symlink") == 0);
auto stat = Stat::lstat("symlink", Stat::OnError::ignore);
CHECK(stat);
+ CHECK(stat.error_number() == 0);
CHECK(!stat.is_directory());
CHECK(!stat.is_regular());
CHECK(stat.is_symlink());
+ CHECK(S_ISLNK(stat.mode()));
+#ifdef _WIN32
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == IO_REPARSE_TAG_SYMLINK);
+#else
CHECK(stat.size() == 4);
+#endif
}
SUBCASE("symlink stat")
{
- REQUIRE(symlink("file", "symlink") == 0);
auto stat = Stat::stat("symlink", Stat::OnError::ignore);
CHECK(stat);
+ CHECK(stat.error_number() == 0);
CHECK(!stat.is_directory());
CHECK(stat.is_regular());
CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
CHECK(stat.size() == 7);
+#ifdef _WIN32
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+#endif
+ }
+}
+
+TEST_CASE("Hard links")
+{
+ TestContext test_context;
+
+ Util::write_file("a", "");
+
+#ifdef _WIN32
+ REQUIRE(CreateHardLinkA("b", "a", nullptr));
+#else
+ REQUIRE(link("a", "b") == 0);
+#endif
+
+ auto stat_a = Stat::stat("a");
+ CHECK(stat_a);
+ CHECK(stat_a.error_number() == 0);
+ CHECK(!stat_a.is_directory());
+ CHECK(stat_a.is_regular());
+ CHECK(!stat_a.is_symlink());
+ CHECK(stat_a.size() == 0);
+
+ auto stat_b = Stat::stat("b");
+ CHECK(stat_b);
+ CHECK(stat_b.error_number() == 0);
+ CHECK(!stat_b.is_directory());
+ CHECK(stat_b.is_regular());
+ CHECK(!stat_b.is_symlink());
+ CHECK(stat_b.size() == 0);
+
+ CHECK(stat_a.device() == stat_b.device());
+ CHECK(stat_a.inode() == stat_b.inode());
+ CHECK(stat_a.same_inode_as(stat_b));
+
+ Util::write_file("a", "1234567");
+ stat_a = Stat::stat("a");
+ stat_b = Stat::stat("b");
+
+ CHECK(stat_a.size() == 7);
+ CHECK(stat_b.size() == 7);
+}
+
+TEST_CASE("Special" * doctest::skip(running_under_wine()))
+{
+ SUBCASE("tty")
+ {
+#ifdef _WIN32
+ auto stat = Stat::stat("\\\\.\\CON");
+#else
+ auto stat = Stat::stat("/dev/tty");
+#endif
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISCHR(stat.mode()));
+#ifdef _WIN32
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
+#endif
+ }
+
+ SUBCASE("null")
+ {
+#ifdef _WIN32
+ auto stat = Stat::stat("\\\\.\\NUL");
+#else
+ auto stat = Stat::stat("/dev/null");
+#endif
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISCHR(stat.mode()));
+#ifdef _WIN32
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
+#endif
+ }
+
+ SUBCASE("pipe")
+ {
+#ifdef _WIN32
+ const char* pipe_path = "\\\\.\\pipe\\InitShutdown"; // Well-known pipe name
+#else
+ const char* pipe_path = "my_pipe";
+ REQUIRE(mkfifo(pipe_path, 0600) == 0);
+#endif
+
+ auto stat = Stat::stat(pipe_path);
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISFIFO(stat.mode()));
+#ifdef _WIN32
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
+#endif
+ }
+
+#ifdef _WIN32
+ SUBCASE("block device")
+ {
+ auto stat = Stat::stat("\\\\.\\C:");
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISBLK(stat.mode()));
+ CHECK(stat.file_attributes() == 0);
+ CHECK(stat.reparse_tag() == 0);
+ }
+#endif
+}
+
+#ifdef _WIN32
+TEST_CASE("Win32 Readonly File")
+{
+ TestContext test_context;
+
+ Util::write_file("file", "");
+
+ DWORD prev_attrs = GetFileAttributesA("file");
+ REQUIRE(prev_attrs != INVALID_FILE_ATTRIBUTES);
+ REQUIRE(SetFileAttributesA("file", prev_attrs | FILE_ATTRIBUTE_READONLY));
+
+ auto stat = Stat::stat("file");
+ REQUIRE(SetFileAttributesA("file", prev_attrs));
+
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(S_ISREG(stat.mode()));
+ CHECK((stat.mode() & ~S_IFMT) == 0444);
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_READONLY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+}
+
+TEST_CASE("Win32 Executable File")
+{
+ TestContext test_context;
+
+ const char* comspec = getenv("COMSPEC");
+ REQUIRE(comspec != nullptr);
+
+ auto stat = Stat::stat(comspec);
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
+ CHECK((stat.mode() & ~S_IFMT) == 0777);
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+}
+
+TEST_CASE("Win32 Pending Delete" * doctest::skip(running_under_wine()))
+{
+ TestContext test_context;
+
+ HANDLE handle =
+ CreateFileA("file",
+ GENERIC_READ | GENERIC_WRITE | DELETE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ nullptr,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+ REQUIRE_MESSAGE(handle != INVALID_HANDLE_VALUE, "err=" << GetLastError());
+ Finalizer cleanup([&] { CloseHandle(handle); });
+
+ // Mark file as deleted. This puts it into a "pending delete" state that
+ // will persist until the handle is closed. Until the file is closed, new
+ // handles cannot be created to the file; attempts to do so fail with
+ // ERROR_ACCESS_DENIED/STATUS_DELETE_PENDING. Our stat implementation maps
+ // these to EACCES. (Should this be ENOENT?)
+ FILE_DISPOSITION_INFO info{};
+ info.DeleteFile = TRUE;
+ REQUIRE_MESSAGE(SetFileInformationByHandle(
+ handle, FileDispositionInfo, &info, sizeof(info)),
+ "err=" << GetLastError());
+
+ SUBCASE("stat file pending delete")
+ {
+ auto st = Stat::stat("file");
+ CHECK(!st);
+ CHECK(st.error_number() == EACCES);
+ }
+
+ SUBCASE("lstat file pending delete")
+ {
+ auto st = Stat::lstat("file");
+ CHECK(!st);
+ CHECK(st.error_number() == EACCES);
+ }
+}
+
+// Our Win32 Stat implementation should open files using FILE_READ_ATTRIBUTES,
+// which bypasses sharing restrictions.
+TEST_CASE("Win32 No Sharing")
+{
+ TestContext test_context;
+
+ HANDLE handle = CreateFileA("file",
+ GENERIC_READ | GENERIC_WRITE,
+ 0 /* no sharing */,
+ nullptr,
+ CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+ REQUIRE_MESSAGE(handle != INVALID_HANDLE_VALUE, "err=" << GetLastError());
+ Finalizer cleanup([&] { CloseHandle(handle); });
+
+ // Sanity check we can't open the file for read/write access.
+ REQUIRE_THROWS_AS(Util::read_file("file"), const Error&);
+
+ SUBCASE("stat file no sharing")
+ {
+ auto stat = Stat::stat("file");
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
+ CHECK(stat.size() == 0);
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+ }
+
+ SUBCASE("lstat file no sharing")
+ {
+ auto stat = Stat::lstat("file");
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISREG(stat.mode()));
+ CHECK(stat.size() == 0);
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+ }
+}
+
+// Creating a directory junction for test purposes is tricky on Windows.
+// Instead, test a well-known junction that has existed in all Windows versions
+// since Vista. (Not present on Wine.)
+TEST_CASE("Win32 Directory Junction"
+ * doctest::skip(!win32_is_junction(Util::expand_environment_variables(
+ "${ALLUSERSPROFILE}\\Application Data"))))
+{
+ TestContext test_context;
+
+ SUBCASE("junction stat")
+ {
+ auto stat = Stat::stat(Util::expand_environment_variables(
+ "${ALLUSERSPROFILE}\\Application Data"));
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink());
+ CHECK(S_ISDIR(stat.mode()));
+ CHECK((stat.mode() & ~S_IFMT) == 0777);
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK(!(stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == 0);
+ }
+
+ SUBCASE("junction lstat")
+ {
+ auto stat = Stat::lstat(Util::expand_environment_variables(
+ "${ALLUSERSPROFILE}\\Application Data"));
+ CHECK(stat);
+ CHECK(stat.error_number() == 0);
+ CHECK(!stat.is_directory());
+ CHECK(!stat.is_regular());
+ CHECK(!stat.is_symlink()); // Should only be true for bona fide symlinks
+ CHECK((stat.mode() & S_IFMT) == 0); // Not a symlink/file/directory
+ CHECK((stat.mode() & ~S_IFMT) == 0777);
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_DIRECTORY));
+ CHECK((stat.file_attributes() & FILE_ATTRIBUTE_REPARSE_POINT));
+ CHECK(stat.reparse_tag() == IO_REPARSE_TAG_MOUNT_POINT);
}
}
#endif
#else
CHECK(data == "carpet\n\n");
#endif
+
+ Util::write_file("size_hint_test", std::string(8192, '\0'));
+ CHECK(Util::read_file("size_hint_test", 4096 /*size_hint*/).size() == 8192);
+
CHECK_THROWS_WITH(Util::read_file("does/not/exist"),
"No such file or directory");
{
#ifdef _WIN32
const char error[] = "failed to rmdir .: Permission denied";
+#elif defined(_AIX)
+ const char error[] = "failed to rmdir .: Device busy";
#else
const char error[] = "failed to rmdir .: Invalid argument";
#endif
CHECK(Win32Util::argv_to_string(argv, "")
== R"("a" "b c" "\"d\"" "'e'" "\\\"h")");
}
+ {
+ const char* const argv[] = {"a\\b\\c", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "") == R"("a\b\c")");
+ }
+ {
+ const char* const argv[] = {"a\\b\\c", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "", true) == R"("a\\b\\c")");
+ }
+ {
+ const char* const argv[] = {R"(a\b \"c\" \)", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "") == R"("a\b \\\"c\\\" \\")");
+ }
+ {
+ const char* const argv[] = {R"(a\b \"c\" \)", nullptr};
+ CHECK(Win32Util::argv_to_string(argv, "", true)
+ == R"("a\\b \\\"c\\\" \\")");
+ }
}
TEST_SUITE_END();
"-Xclang -fno-pch-timestamp"
" -Xclang unsupported";
+ const std::string color_diag = "-Xclang -fcolor-diagnostics";
+
const std::string extra_args =
"-Xclang -emit-pch"
" -Xclang -emit-pth";
" -Xclang -include-pth pth_path1"
" -Xclang -include-pth -Xclang pth_path2";
- ctx.orig_args = Args::from_string("gcc -c foo.c " + common_args + " "
- + extra_args + " " + pch_pth_variants);
+ ctx.orig_args =
+ Args::from_string("gcc -c foo.c " + common_args + " " + color_diag + " "
+ + extra_args + " " + pch_pth_variants);
Util::write_file("foo.c", "");
const ProcessArgsResult result = process_args(ctx);
== "gcc " + common_args + " " + pch_pth_variants);
CHECK(result.extra_args_to_hash.to_string() == extra_args);
CHECK(result.compiler_args.to_string()
- == "gcc " + common_args + " " + extra_args + " " + pch_pth_variants
- + " -c");
+ == "gcc " + common_args + " " + color_diag + " " + extra_args + " "
+ + pch_pth_variants + " -c");
}
TEST_CASE("-x")