Imported Upstream version 4.2.1 upstream/4.2.1
authorJinWang An <jinwang.an@samsung.com>
Tue, 3 Aug 2021 07:30:39 +0000 (16:30 +0900)
committerJinWang An <jinwang.an@samsung.com>
Tue, 3 Aug 2021 07:30:39 +0000 (16:30 +0900)
41 files changed:
.github/workflows/build.yaml
.github/workflows/codeql-analysis.yaml
.mailmap
CMakeLists.txt
LICENSE.adoc
cmake/CcacheVersion.cmake
cmake/Findzstd.cmake
cmake/StdAtomic.cmake
cmake/UseCcache.cmake
cmake/UseFastestLinker.cmake
cmake/config.h.in
doc/AUTHORS.adoc
doc/CMakeLists.txt
doc/INSTALL.md
doc/MANUAL.adoc
doc/NEWS.adoc
src/CacheEntryReader.cpp
src/CacheEntryReader.hpp
src/CacheEntryWriter.cpp
src/CacheEntryWriter.hpp
src/Depfile.cpp
src/Stat.cpp
src/Stat.hpp
src/Util.cpp
src/Win32Util.cpp
src/Win32Util.hpp
src/argprocessing.cpp
src/ccache.cpp
src/compopt.cpp
src/execute.cpp
src/execute.hpp
src/system.hpp
src/third_party/doctest.h
src/third_party/win32/winerror_to_errno.h [new file with mode: 0644]
test/run
test/suites/base.bash
test/suites/color_diagnostics.bash
unittest/test_Stat.cpp
unittest/test_Util.cpp
unittest/test_Win32Util.cpp
unittest/test_argprocessing.cpp

index c222a199cfa25729f558508bf2b967d0fac50480..662f491d050d0e8edd7c17355cbe6e70d8a2448c 100644 (file)
@@ -92,11 +92,10 @@ jobs:
             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
@@ -160,7 +159,7 @@ jobs:
             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
@@ -180,7 +179,7 @@ jobs:
             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
@@ -258,13 +257,13 @@ jobs:
             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
index 972b6b9a14e194779836ecc95a55b9863363f018..b19e807632d29d79a3808663342f7d3d4d3c924f 100644 (file)
@@ -31,7 +31,7 @@ jobs:
         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
index 01d779746a8f0e5fca84c7367e417b813be88efa..cc87e37d72856ded9684d7131f872118240cfbbd 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -18,6 +18,7 @@ Luboš Luňák <l.lunak@centrum.cz> <l.lunak@suse.cz>
 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>
index 40e21a57120414e9b88dec92fc6c85676a84d280..fc2ff6657352429bd8bd82ea04500b04fe592175 100644 (file)
@@ -66,7 +66,9 @@ endif()
 message(STATUS "Ccache dev mode: ${CCACHE_DEV_MODE}")
 
 include(UseCcache)
-include(UseFastestLinker)
+if(NOT MSVC)
+  include(UseFastestLinker)
+endif()
 include(StandardSettings)
 include(StandardWarnings)
 include(CIBuildType)
index cbce985db819c80fa8b7f31ab708761e1dec98b9..4c6df86f88a1b40b3f64bfd35eddaed585448771 100644 (file)
@@ -421,12 +421,12 @@ src/third_party/doctest.h
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
 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
@@ -662,6 +662,63 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 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*
 ~~~~~~~~~~~~~~~~~~~~
 
index 386d6ca8018e5ed0a6ada9724a8db2e995f6f574..2568fd57798127c08afdfdca4b89c2c43398681c 100644 (file)
@@ -22,7 +22,7 @@
 # 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.
index 0044937b3e54c23320e757ff4727f9015785d117..e889a680923091d34be6b77d0388d0a0b759eb93 100644 (file)
@@ -6,7 +6,7 @@ if(ZSTD_FROM_INTERNET)
   # 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})
index aa3bb4bff02c0c37d3f387c7110c45ecb38978e3..8cebe515709f3ac00eb8430bd36db12c07a4052a 100644 (file)
@@ -10,6 +10,7 @@ set(
     int main()
     {
       std::atomic<long long> x;
+      ++x;
       (void)x.load();
       return 0;
     }
index e89339af954e8c924c0142c4617decd6b8a7a946..3137d8578f4c58fb9f7732a6824c9a66260d923a 100644 (file)
@@ -63,6 +63,11 @@ function(use_ccache)
   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()
index c96639cb13c5bba025f9f83854456a22a36e63e1..ffc26ea29054074ae2f8b75c963543dce7db64de 100644 (file)
@@ -1,9 +1,12 @@
-# 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)
@@ -11,20 +14,23 @@ 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()
 
index b4e412f7f07f5dcb2e0f4de227bbd3c99e753b4b..d10e16417f8f05bb42f8fb05897ee3c7c300dcf3 100644 (file)
@@ -34,6 +34,9 @@
 #  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
index dc3717c4d5cc72cb988972bc63522a6ed37d8db7..ce44c8c8e40dab9b01271541a167f56ec986000c 100644 (file)
@@ -6,6 +6,8 @@ maintained by Joel Rosdahl.
 
 Ccache is a collective work with contributions from many people, including:
 
+* Abubakar Nur Khalil
+* Aleksander Salwa
 * Alexander Korsunsky
 * Alexander Lanin
 * Alexey Tourbin
@@ -33,6 +35,7 @@ Ccache is a collective work with contributions from many people, including:
 * Edward Z. Yang
 * Enrico Sorichetti
 * Erik Flodin
+* Evangelos Foutras
 * Francois Marier
 * Gabriel Scherer
 * Geert Bosch
@@ -52,6 +55,7 @@ Ccache is a collective work with contributions from many people, including:
 * John Coiner
 * Jon Bernard
 * Jonny Yu
+* Jon Petrissans
 * Jørgen P. Tjernø
 * Josh Soref
 * Justin Lebar
@@ -99,6 +103,7 @@ Ccache is a collective work with contributions from many people, including:
 * Paul Griffith
 * Pavel Boldin
 * Pavol Sakac
+* Pawel Krysiak
 * Per Nordlöw
 * Peter Budai
 * Philippe Proulx
index dda47033aa60698d380248e63150f2066d38c371..c5ce224d9e99ff317e45f9ccf627107169da3fa6 100644 (file)
@@ -2,7 +2,7 @@ find_program(ASCIIDOC_EXE asciidoc)
 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
index 8a22618dfff8f720875b195e63cbec4f5b2817e7..a90a24f92a7a22930ba39bb5eb59a5031fb5f93d 100644 (file)
@@ -11,7 +11,7 @@ To build ccache you need:
   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.
index 98aae9a3bfec1bfcdb830d9c9bba7ca9cad80cf8..2b6f0012883c348410369e34221cedb345130f2d 100644 (file)
@@ -478,7 +478,7 @@ wrappers>>_.
     GCC-based compiler.
 *nvcc*::
     NVCC (CUDA) compiler.
-*other::
+*other*::
     Any compiler other than the known types.
 *pump*::
     distcc's "pump" script.
@@ -557,11 +557,11 @@ See the http://zstd.net[Zstandard documentation] for more information.
     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)::
 
@@ -1049,7 +1049,7 @@ The compiler to execute could not be found.
 
 | 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.
@@ -1134,7 +1134,7 @@ The following information is always included in the hash:
 * 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
index a4b3af8a2a3b9a396c63d21a4399b22c39740aae..040a68e3a994efd6d4327c1ca3100d2cb61733d2 100644 (file)
@@ -1,6 +1,71 @@
 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
@@ -18,7 +83,7 @@ New features
 
 - 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`.
 
index c6964ad67ed9cc12d2a96fc9f0b4e6f2bbf07c3d..36dd3931ee210d9d62438d6b50c7a1f913ea3d0c 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -25,7 +25,7 @@
 #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];
index 8ee73023235d81aeb4ac8834b48d3ca2e87d8629..37db80f5e62ba54f98917d57f0e27ab26900e524 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -36,10 +36,11 @@ public:
   //
   // 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.
index 6636bf556027120398c32c230415b84ecba9cd5b..8f45adfa9eebf5df101d0d3f4274abe0f3d7c0d5 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -19,7 +19,7 @@
 #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,
index 14c0d19e6e63e73059c6889eb88a2aec0c1e2c56..dc9226f61ac3452eaa33870c2782612d1abb8c0e 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -36,13 +36,13 @@ public:
   //
   // 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,
index ed491f4dc17a14d3a9a03f340c8839df1ecb65c2..13106ce7fb576cb7c092ba043de5c3bc5eac0b18 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -183,12 +183,10 @@ tokenize(nonstd::string_view file_content)
     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;
index ece4cc4c08e2ae0e5826473bd83df52677f35e28..d491682937112bd0e703da72e7566e5cefd3579f 100644 (file)
 
 #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)
@@ -41,3 +214,29 @@ Stat::Stat(StatFunction stat_function,
     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);
+}
index 721b824a50011a08724635970c6b8816854afa27..5a046d2ab82503ebddfa502d7639025a750c7149 100644 (file)
@@ -38,6 +38,29 @@ public:
     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();
@@ -81,21 +104,21 @@ public:
   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;
@@ -106,25 +129,6 @@ inline Stat::Stat() : m_stat{}, m_errno(-1)
 {
 }
 
-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;
@@ -142,13 +146,13 @@ Stat::error_number() const
   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;
@@ -163,13 +167,13 @@ Stat::mode() const
 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
@@ -197,11 +201,7 @@ Stat::is_directory() const
 inline bool
 Stat::is_symlink() const
 {
-#ifndef _WIN32
   return S_ISLNK(mode());
-#else
-  return false;
-#endif
 }
 
 inline bool
@@ -210,18 +210,36 @@ Stat::is_regular() const
   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
+}
index 71de5f9cdb57fc49c335deb5654e280e4eb54d98..f49708fd4653d35352a3e54d776e82a73372e844 100644 (file)
@@ -1180,7 +1180,7 @@ read_file(const std::string& path, size_t size_hint)
   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;
index 354c9ae32e08c9d009f954bb4b1180cb8542581f..b259ed0e98d159215fa61c0e9e1118df80863a89 100644 (file)
@@ -55,7 +55,9 @@ error_message(DWORD error_code)
 }
 
 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;
@@ -67,9 +69,11 @@ argv_to_string(const char* const* argv, const std::string& prefix)
     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.
index a56387d1f02ef07e153d22ada4b6b0cbfbc14549..d6fc57595a10c8839f13c5971a8ef768d01694a1 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -29,8 +29,12 @@ namespace Win32Util {
 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);
index 612eadd7e54319ec0ccc3a8a3e559ccf4520ee5b..e39c4e54fd062bb403158a47e1c1c37caa2bd215 100644 (file)
@@ -84,6 +84,9 @@ struct ArgumentProcessingState
   // 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
@@ -687,19 +690,27 @@ process_arg(Context& ctx,
     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;
@@ -754,6 +765,10 @@ process_arg(Context& ctx,
     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.
@@ -955,18 +970,51 @@ process_args(Context& ctx)
 
   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()) {
@@ -1007,6 +1055,10 @@ process_args(Context& ctx)
     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(
@@ -1054,22 +1106,6 @@ process_args(Context& ctx)
     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");
   }
@@ -1220,6 +1256,9 @@ process_args(Context& ctx)
   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);
index 10724c95df026669783a9f6270385cc13da04bf8..7d39e5bbc273f5cc03efcd6f2fcb72190983ef08 100644 (file)
@@ -64,7 +64,9 @@
 #elif defined(_WIN32)
 #  include "third_party/win32/getopt.h"
 #else
+extern "C" {
 #  include "third_party/getopt_long.h"
+}
 #endif
 
 #ifdef _WIN32
@@ -781,10 +783,10 @@ do_execute(Context& ctx,
     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);
@@ -2284,6 +2286,7 @@ cache_compilation(int argc, const char* const* argv)
   bool fall_back_to_original_compiler = false;
   Args saved_orig_args;
   nonstd::optional<mode_t> original_umask;
+  std::string saved_temp_dir;
 
   {
     Context ctx;
@@ -2319,10 +2322,12 @@ cache_compilation(int argc, const char* const* argv)
 
       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.
     }
   }
 
@@ -2331,8 +2336,9 @@ cache_compilation(int argc, const char* const* argv)
       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;
@@ -2369,10 +2375,6 @@ do_cache_compilation(Context& ctx, const char* const* argv)
     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);
@@ -2390,6 +2392,8 @@ do_cache_compilation(Context& ctx, const char* const* argv)
     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"
index 1931d5f442be66e56b811b1c3c8ea30fbf898b62..d69f74aa852fcaec45536e4c3f688cb1af5588b4 100644 (file)
@@ -1,4 +1,4 @@
-// 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.
 //
@@ -102,6 +102,7 @@ const CompOpt compopts[] = {
   {"-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},
index ec8703391d760fa55011f65058fb948d73540447..ebcf2becbeab1c6372e0a98e28a9126845e7e962 100644 (file)
@@ -1,5 +1,5 @@
 // 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
@@ -70,7 +89,8 @@ win32execute(const char* path,
              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));
@@ -108,11 +128,20 @@ win32execute(const char* path,
   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()),
@@ -124,9 +153,6 @@ win32execute(const char* path,
                            nullptr,
                            &si,
                            &pi);
-  if (!tmp_file_path.empty()) {
-    Util::unlink_tmp(tmp_file_path);
-  }
   if (fd_stdout != -1) {
     close(fd_stdout);
     close(fd_stderr);
@@ -156,20 +182,20 @@ win32execute(const char* path,
 // 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();
@@ -184,7 +210,7 @@ execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
   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;
     }
@@ -193,7 +219,7 @@ execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
 
   {
     SignalHandlerBlocker signal_handler_blocker;
-    *pid = 0;
+    ctx.compiler_pid = 0;
   }
 
   if (WEXITSTATUS(status) == 0 && WIFSIGNALED(status)) {
@@ -202,6 +228,12 @@ execute(const char* const* argv, Fd&& fd_out, Fd&& fd_err, pid_t* pid)
 
   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
index 9814a73c69a874179137c6a017426236b199a490..628ceb32753e529a01665474a50a146ef654c84e 100644 (file)
@@ -26,7 +26,9 @@
 
 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`.
@@ -40,9 +42,4 @@ std::string find_executable_in_path(const std::string& 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
index ae4ca521e4b674b8e905fb887f19a3846c06f4df..6115c5ba5a214776c70de308ce5307342c3e6c0d 100644 (file)
@@ -80,7 +80,7 @@
 // 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]))
@@ -123,15 +123,36 @@ const mode_t S_IRUSR = mode_t(_S_IREAD);
 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>
@@ -139,7 +160,9 @@ const mode_t S_IWUSR = mode_t(_S_IWRITE);
 #  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
 
index 7712dd6b6322591a8c52b676a90d880c84359ac7..42eb039979bed3c573c3fcce35030f6810e26b52 100644 (file)
@@ -4,7 +4,7 @@
 //
 // 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
@@ -48,8 +48,8 @@
 
 #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)
@@ -354,7 +354,7 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
 #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
@@ -362,16 +362,16 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
 #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()
@@ -656,12 +656,14 @@ DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file);
 
 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;
@@ -715,12 +717,18 @@ struct DOCTEST_INTERFACE IContextScope
     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
@@ -773,6 +781,29 @@ namespace detail {
     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
@@ -1040,10 +1071,20 @@ namespace detail {
         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)                                          \
@@ -1171,12 +1212,16 @@ namespace detail {
         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;
 
@@ -1185,6 +1230,10 @@ namespace detail {
             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
@@ -1225,6 +1274,10 @@ namespace detail {
 
 #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;
@@ -1236,8 +1289,8 @@ namespace detail {
         // 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);
         }
     };
 
@@ -1246,6 +1299,8 @@ namespace detail {
         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;
@@ -1524,7 +1579,7 @@ namespace detail {
 
     template <typename L> class ContextScope : public ContextScopeBase
     {
-        const L &lambda_;
+        const L lambda_;
 
     public:
         explicit ContextScope(const L &lambda) : lambda_(lambda) {}
@@ -1585,6 +1640,8 @@ namespace detail {
 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);
@@ -1921,10 +1978,12 @@ int registerReporter(const char* name, int priority, bool isReporter) {
             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;                                                                 \
@@ -1979,17 +2038,15 @@ int registerReporter(const char* name, int priority, bool isReporter) {
 // 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)
 
@@ -2461,7 +2518,7 @@ int registerReporter(const char* name, int priority, bool isReporter) {
 #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
@@ -2481,138 +2538,138 @@ int registerReporter(const char* name, int priority, bool isReporter) {
 // == 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
 
@@ -2689,6 +2746,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers")
 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")
@@ -2794,11 +2852,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN
 #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>
 
@@ -2834,12 +2888,24 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END
 #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;
@@ -2972,18 +3038,105 @@ typedef timer_large_integer::type ticks_t;
         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;
@@ -3093,14 +3246,16 @@ String::String(const char* in)
 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';
     }
 }
 
@@ -3471,7 +3626,7 @@ int registerReporter(const char*, int, IReporter*) { return 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
@@ -3583,7 +3738,7 @@ namespace detail {
 
     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)) {
@@ -3625,7 +3780,7 @@ namespace detail {
                 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()
@@ -3660,6 +3815,8 @@ namespace detail {
         // 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;
@@ -3675,6 +3832,8 @@ namespace detail {
         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;
@@ -3721,14 +3880,20 @@ namespace detail {
         // 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;
@@ -3760,12 +3925,6 @@ namespace {
         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;
@@ -3994,7 +4153,7 @@ namespace detail {
     // 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()) {
@@ -4016,7 +4175,9 @@ namespace {
 #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
 
@@ -4069,6 +4230,9 @@ namespace {
             std::exit(EXIT_FAILURE);
         }
 
+        static void allocateAltStackMem() {}
+        static void freeAltStackMem() {}
+
         FatalConditionHandler() {
             isSet = true;
             // 32k seems enough for doctest to handle stack overflow,
@@ -4086,7 +4250,7 @@ namespace {
             // - 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();
@@ -4097,7 +4261,7 @@ namespace {
             // - 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)
@@ -4135,8 +4299,8 @@ namespace {
                 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;
             }
         }
@@ -4186,7 +4350,8 @@ namespace {
         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>";
@@ -4202,11 +4367,19 @@ namespace {
             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 = {};
@@ -4231,10 +4404,11 @@ namespace {
         }
     };
 
-    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
@@ -4336,8 +4510,8 @@ namespace detail {
             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 {
@@ -4387,7 +4561,8 @@ namespace detail {
             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() {
@@ -5495,7 +5670,7 @@ namespace {
               << 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>                   "
@@ -5668,6 +5843,9 @@ namespace {
         }
 
         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 ||
@@ -5702,6 +5880,9 @@ namespace {
         }
 
         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, " ");
@@ -5736,7 +5917,7 @@ namespace {
         }
 
         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);
@@ -5752,6 +5933,9 @@ namespace {
         }
 
         void log_message(const MessageData& mb) override {
+            if(tc->m_no_output)
+                return;
+
             std::lock_guard<std::mutex> lock(mutex);
 
             logTestStart();
@@ -5782,8 +5966,10 @@ namespace {
         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;                                                                    \
     }
 
@@ -5970,7 +6156,7 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) {
 #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;                                                                             \
@@ -6115,7 +6301,11 @@ int Context::run() {
         p->cout = &fstr;
     }
 
+    FatalConditionHandler::allocateAltStackMem();
+
     auto cleanup_and_return = [&]() {
+        FatalConditionHandler::freeAltStackMem();
+
         if(fstr.is_open())
             fstr.close();
 
@@ -6187,6 +6377,9 @@ int Context::run() {
                 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
         }
     }
 
@@ -6286,10 +6479,13 @@ int Context::run() {
 #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;
diff --git a/src/third_party/win32/winerror_to_errno.h b/src/third_party/win32/winerror_to_errno.h
new file mode 100644 (file)
index 0000000..6b53b63
--- /dev/null
@@ -0,0 +1,151 @@
+#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
index 224494390a9b4dcd04b7f1a636200ff10052f433..cbdd98f0622ff4355587fb8c8781e2f2fb811b44 100755 (executable)
--- a/test/run
+++ b/test/run
@@ -3,7 +3,7 @@
 # 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.
 #
@@ -43,11 +43,15 @@ bold() {
     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
@@ -59,6 +63,11 @@ test_failed() {
     exit 1
 }
 
+# Indirection so the line returned by `caller` is correct.
+test_failed() {
+    test_failed_internal "$@"
+}
+
 find_compiler() {
     local name=$1
     perl -e '
@@ -163,62 +172,62 @@ expect_stat() {
     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
@@ -250,7 +259,7 @@ is_equal_object_files() {
 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
 }
 
@@ -259,10 +268,10 @@ expect_content() {
     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
 }
 
@@ -271,10 +280,10 @@ expect_contains() {
     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
 }
 
@@ -283,10 +292,10 @@ expect_not_contains() {
     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
 }
 
@@ -295,7 +304,7 @@ expect_objdump_contains() {
     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
 }
 
@@ -304,7 +313,7 @@ expect_objdump_not_contains() {
     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
 }
 
@@ -314,7 +323,7 @@ expect_file_count() {
     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
 }
 
@@ -323,7 +332,7 @@ expect_newer_than() {
     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
 }
 
@@ -332,7 +341,7 @@ expect_perm() {
     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
 }
 
index e16741ce1d32cadc8b4feea90f1c37d5007827ef..9629a06d3b27a4541f689f2f182696583ed97bd4 100644 (file)
@@ -64,7 +64,7 @@ base_tests() {
     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"
@@ -351,6 +351,13 @@ fi
         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"
 
@@ -752,6 +759,27 @@ b"
     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"
 
@@ -1357,7 +1385,7 @@ fi
 
     # -------------------------------------------------------------------------
 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
@@ -1389,6 +1417,14 @@ EOF
     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"
 
index 4ec99f4022e1992ef8ab0ebe220a624d381f68e1..7c83714c4b503208993a0d5d8765b785ef9855ed 100644 (file)
@@ -122,6 +122,7 @@ color_diagnostics_test() {
         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"
@@ -135,6 +136,30 @@ color_diagnostics_test() {
         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
index c61a62c0bb6c2eaf0022f3e3790f4a6411b82a78..6f8baa6495537253af292a742ae92aa14cf7da86 100644 (file)
@@ -16,6 +16,7 @@
 // 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")
@@ -46,14 +158,15 @@ 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
 }
 
@@ -76,11 +189,7 @@ TEST_CASE("Same i-node as")
   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");
@@ -103,14 +212,15 @@ TEST_CASE("Return values when file is missing")
   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
 }
 
@@ -121,34 +231,66 @@ TEST_CASE("Return values when file exists")
   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
 }
 
@@ -164,55 +306,390 @@ TEST_CASE("Directory")
   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
index 5fd52399d7a7e54fc7258273dbf039b4263d463d..cb2be415c2d126cde905a236b92dd7a321c93fde 100644 (file)
@@ -820,6 +820,10 @@ TEST_CASE("Util::read_file and Util::write_file")
 #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");
 
@@ -1080,6 +1084,8 @@ TEST_CASE("Util::wipe_path")
   {
 #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
index eb7dc8f59020dacc33b126304b01926b1840f253..f836171c61c7977f3a7cb3917576a95fd70acdce 100644 (file)
@@ -38,6 +38,23 @@ TEST_CASE("Win32Util::argv_to_string")
     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();
index 7db1ad3ca29be6b96fd3312d7489c7aeec0d97f1..829a7a746805ca92624ea994aeef7fd9879e1e2b 100644 (file)
@@ -570,6 +570,8 @@ TEST_CASE("-Xclang")
     "-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";
@@ -580,8 +582,9 @@ TEST_CASE("-Xclang")
     " -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);
@@ -589,8 +592,8 @@ TEST_CASE("-Xclang")
         == "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")