google / googletest

GoogleTest - Google Testing and Mocking Framework
https://google.github.io/googletest/
BSD 3-Clause "New" or "Revised" License
34.96k stars 10.17k forks source link

[Bug]: Compile break for chrono timepoint formatting for macOS target < 13.3 and Xcode 15.3 #4516

Open wAuner opened 7 months ago

wAuner commented 7 months ago

Describe the issue

Problem: With the latest version of Xcode 15.3 it is no longer possible to use chrono timpoints in a test assertion, if the build target is lower than macOS 13.3. With Xcode 15.0.1 (and possibly later) this worked. The macOS SDK made the std::to_chars function available with macOS 13.3, which seems to be involved when formatting the timepoints for printing. Before that, Google Test maybe used a workaround for formatting that's no longer necessary for newer targets? This workaround is still needed for older deployment targets. CleanShot 2024-04-16 at 11 18 16@2x

Steps to reproduce the problem

I've created an example project here. The build breaks with Xcode 15.3 when the deployment target is set below 13.3, which wasn't a problem with Xcode 15.0.1.

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)
# build fails if deployment target is set below 13.3
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0")
set(CMAKE_CXX_STANDARD 20)

project(gtest_issue)

include(FetchContent)
FetchContent_Declare(
    googletest
    URL https://github.com/google/googletest/archive/5197b1a8e6a1ef9f214f4aa537b0be17cbf91946.zip # latest commit
    # URL https://github.com/google/googletest/archive/f8d7d77c06936315286eb55f8de22cd23c188571.zip # 1.14 release
    )

FetchContent_MakeAvailable(googletest)

enable_testing()

add_executable(gtest_issue macostest.cpp)
target_link_libraries(gtest_issue gtest_main)

include(GoogleTest)
gtest_discover_tests(gtest_issue)
#include <gtest/gtest.h>
#include <chrono>

TEST(ChronoTest, CompileBreakBelowMacOS133) {

    auto t1 = std::chrono::system_clock::now();
    ASSERT_EQ(t1, t1);
}

TEST(ChronoTest, Success) {

    auto t1 = std::chrono::system_clock::now();
    ASSERT_EQ(t1.time_since_epoch().count(), t1.time_since_epoch().count());
}

The deployment target of the mach-o can be verified via otool -l gtest_issue | grep -A 4 LC_BUILD_VERSION

What version of GoogleTest are you using?

latest commit 5197b1a8e6a1ef9f214f4aa537b0be17cbf91946

What operating system and version are you using?

macOS 14.4.1 Xcode 15.3 (macOS SDK 14.4)

What compiler and version are you using?

clang --version
Apple clang version 15.0.0 (clang-1500.3.9.4)
Target: arm64-apple-darwin23.4.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin

What build system are you using?

Xcode / cmake

Additional context

No response

mjburghard commented 5 months ago

I am encountering the same issue and have not found any fix apart from raising the deployment target for our test executor to 13.3.

While looking into this, I added @wAuner's CompileBreakBelowMacOS133 test to /googletest/test/googletest-printers-test.cc to try to reproduce the issue in the repository itself. Interestingly, I am not able to reproduce it here. Neither with cmake/ninja, nor with bazel:

Ninja:

cmake .. -G Ninja -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 && ninja

Successfully compiles, the tests succeed, and otool also shows the correct deployment target:

otool -l googletest/googletest-printers-test 
...
     cmd LC_BUILD_VERSION
  cmdsize 32
 platform 1
    minos 11.0
      sdk 14.5
   ntools 1
     tool 3
  version 1053.12
...

Bazel:

bazelisk test '//googletest/test:gtest_all_test' --macos_minimum_os=11.0

Successfully compiles, the tests succeed, and otool also shows the correct deployment target:

otool -l bazel-out/darwin_arm64-fastbuild/bin/googletest/test/gtest_all_test
...
      cmd LC_BUILD_VERSION
  cmdsize 32
 platform 1
    minos 11.0
      sdk 14.5
   ntools 1
     tool 3
  version 1053.12
...
mjburghard commented 5 months ago

I found a good workaround in case anybody else is running into this, too. googletest provide a customization point test/internal/custom. Adding the following googletest/include/gtest/internal/custom/gtest-printers.h resolves the error for my use case and brings back the old behavior before the introduction of std::to_chars:

#include "gtest/gtest-printers.h"

#ifdef _LIBCPP_AVAILABILITY_HAS_NO_TO_CHARS_FLOATING_POINT 

namespace std {

inline void PrintTo(const ::std::chrono::time_point<std::chrono::system_clock>& value, ::std::ostream* os) {
    ::testing::internal::RawBytesPrinter::PrintValue(value, os);
}

} // namespace std

#endif
kuvaibhavmsft commented 3 months ago

In case anyone else runs into this, I had to use

include

if defined(MACOS) && __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_13_3

instead of

ifdef _LIBCPP_AVAILABILITY_HAS_NO_TO_CHARS_FLOATING_POINT

on Xcode 16 beta 6 in the solution suggested by @mjburghard above.

dani-sev commented 1 month ago

The same problem happens for IOS Target version < 16.3

In case anyone else has this problem, below is a code snippet, integrating of both @kuvaibhavmsft and @mjburghard answers to fix macos<13.3 and ios<16.3.

it was tested on Xcode 16.0 release

#if defined(__APPLE__)
    // Include Availability.h for version macros
    #include <Availability.h>
    #include "gtest/gtest-printers.h"

    // Define __MAC_13_3 if not defined (macOS 13.3)
    #ifndef __MAC_13_3
        #define __MAC_13_3 130300
    #endif

    // Define __IPHONE_16_3 if not defined (iOS 16.3)
    #ifndef __IPHONE_16_3
        #define __IPHONE_16_3 160300
    #endif

    // Check for macOS versions less than 13.3 or iOS versions less than 16.3
    #if (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_13_3)) || \
        (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_16_3))

        namespace std {

            inline void PrintTo(const ::std::chrono::time_point<std::chrono::system_clock>& value,
                                ::std::ostream* os)
            {
                ::testing::internal::RawBytesPrinter::PrintValue(value, os);
            }

        } // namespace std

    #endif
#endif

The above code should be added to googletest/include/gtest/internal/custom/gtest-printers.h