diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dca6c31bc..1a3b121b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -257,6 +257,9 @@ jobs: arch: [i686, x86_64] config: [Debug, Release] runs-on: windows-latest + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 steps: - uses: actions/checkout@v3 @@ -277,6 +280,8 @@ jobs: } cmake ../ -G"MinGW Makefiles" -DCMAKE_BUILD_TYPE=${{ matrix.config }} ` -DDOWNLOAD_WINDOWSNUMERICS=TRUE ` + -DUSE_ANSI_COLOR=TRUE ` + -DCMAKE_CXX_FLAGS="-fansi-escape-codes" ` -DENABLE_TEST_SANITIZERS=$sanitizers cmake --build . -j2 --target cppwinrt @@ -303,12 +308,69 @@ jobs: $env:UBSAN_OPTIONS = "print_stacktrace=1" ctest --verbose + test-msys2-gcc-cppwinrt: + name: 'gcc/msys2: Build and test (${{ matrix.sys }}, ${{ matrix.config }})' + strategy: + fail-fast: false + matrix: + include: + - { sys: mingw32, arch: i686, config: Release } + - { sys: mingw64, arch: x86_64, config: Debug } + - { sys: mingw64, arch: x86_64, config: Release } + - { sys: ucrt64, arch: x86_64, config: Release } + runs-on: windows-latest + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 + defaults: + run: + shell: msys2 {0} + steps: + - uses: msys2/setup-msys2@v2 + with: + msystem: ${{matrix.sys}} + update: true + pacboy: >- + crt:p + gcc:p + binutils:p + cmake:p + ninja:p + + - uses: actions/checkout@v3 + + - name: Build cppwinrt + run: | + mkdir build + cd build + if [[ "${{ matrix.arch }}" = "i686" ]]; then + skip_large_pch_arg="-DSKIP_LARGE_PCH=TRUE" + fi + cmake ../ -GNinja -DCMAKE_BUILD_TYPE=${{ matrix.config }} \ + -DDOWNLOAD_WINDOWSNUMERICS=TRUE \ + -DUSE_ANSI_COLOR=TRUE \ + $skip_large_pch_arg + cmake --build . --target cppwinrt + + - name: Build tests + run: | + cd build + cmake --build . -j2 --target test-vanilla test_cpp20 test_win7 test_old + + - name: Run tests + run: | + cd build + ctest --verbose + build-linux-cross-cppwinrt: name: 'cross: Cross-build from Linux' strategy: matrix: arch: [i686, x86_64] runs-on: ubuntu-22.04 + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 steps: - uses: actions/checkout@v3 @@ -367,7 +429,8 @@ jobs: cd build cmake ../test -G"MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug ` -DCPPWINRT_PROJECTION_INCLUDE_DIR="../.test/out" ` - -DDOWNLOAD_WINDOWSNUMERICS=TRUE + -DDOWNLOAD_WINDOWSNUMERICS=TRUE ` + -DUSE_ANSI_COLOR=TRUE cmake --build . -j2 - name: Run tests @@ -376,11 +439,35 @@ jobs: ctest --verbose build-linux-native-cppwinrt: - name: 'linux: GCC native build + llvm-mingw cross-build tests' + name: 'linux: GCC native build + mingw-w64 cross-build tests' + strategy: + fail-fast: false + matrix: + # TODO: Enable gcc build once Arch Linux gets more recent mingw-w64 headers (ver. 11 perhaps?) + # cross_toolchain: [gcc, llvm-mingw] + cross_toolchain: [llvm-mingw] + cross_arch: [i686, x86_64] + # include: + # - cross_toolchain: gcc + # container: + # image: archlinux:base-devel runs-on: ubuntu-22.04 + container: ${{ matrix.container }} + defaults: + run: + shell: bash + env: + CMAKE_COLOR_DIAGNOSTICS: 1 + CLICOLOR_FORCE: 1 steps: - uses: actions/checkout@v3 + - name: Install build tools + if: matrix.cross_toolchain == 'gcc' + run: | + pacman --noconfirm -Suuy + pacman --needed --noconfirm -S cmake ninja git + - name: Build cppwinrt run: | cmake -S . -B build/native/ \ @@ -399,30 +486,41 @@ jobs: - id: setup-llvm name: Set up llvm-mingw + if: matrix.cross_toolchain == 'llvm-mingw' uses: ./.github/actions/setup-llvm-mingw + - name: Install GCC cross compiler + if: matrix.cross_toolchain == 'gcc' + run: | + pacman --needed --noconfirm -S mingw-w64-gcc + - name: Cross-build tests using projection run: | cmake -S test -B build/cross-tests --toolchain "$PWD/cross-mingw-toolchain.cmake" \ - -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ + -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.cross_arch }} \ -DCMAKE_BUILD_TYPE=Debug \ -DCMAKE_CXX_FLAGS="-static" \ -DCPPWINRT_PROJECTION_INCLUDE_DIR=/tmp/cppwinrt \ - -DDOWNLOAD_WINDOWSNUMERICS=TRUE + -DDOWNLOAD_WINDOWSNUMERICS=TRUE \ + -DUSE_ANSI_COLOR=TRUE cmake --build build/cross-tests -j2 - name: Upload built tests uses: actions/upload-artifact@v3 with: - name: linux-native-cppwinrt-cross-build-tests-x86_64-bin + name: linux-native-cppwinrt-cross-build-tests-${{ matrix.cross_toolchain }}-${{ matrix.cross_arch }}-bin path: build/cross-tests/*.exe test-linux-native-cppwinrt-cross-tests: name: 'linux: Run llvm-mingw cross-build tests' needs: build-linux-native-cppwinrt strategy: + fail-fast: false matrix: - arch: [x86_64] + # TODO: Enable gcc build test when it is buildable + # cross_toolchain: [gcc, llvm-mingw] + cross_toolchain: [llvm-mingw] + cross_arch: [i686, x86_64] runs-on: windows-latest steps: - uses: actions/checkout@v3 @@ -430,7 +528,7 @@ jobs: - name: Fetch test executables uses: actions/download-artifact@v3 with: - name: linux-native-cppwinrt-cross-build-tests-${{ matrix.arch }}-bin + name: linux-native-cppwinrt-cross-build-tests-${{ matrix.cross_toolchain }}-${{ matrix.cross_arch }}-bin path: ./ - name: Run tests @@ -439,7 +537,7 @@ jobs: $has_failed_tests = 0 foreach ($test_exe in $test_exes) { echo "::group::Run '$test_exe'" - & .\$test_exe + & .\$test_exe --use-colour yes echo "::endgroup::" if ($LastExitCode -ne 0) { echo "::error::Test '$test_exe' failed!" diff --git a/CMakeLists.txt b/CMakeLists.txt index e8ef5fa4a..b5f547fd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,6 +182,7 @@ ExternalProject_Add(winmd CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" + UPDATE_COMMAND "" ) add_dependencies(cppwinrt winmd) ExternalProject_Get_Property(winmd SOURCE_DIR) diff --git a/strings/base_chrono.h b/strings/base_chrono.h index 151246d46..d48cd703b 100644 --- a/strings/base_chrono.h +++ b/strings/base_chrono.h @@ -42,12 +42,12 @@ WINRT_EXPORT namespace winrt static time_t to_time_t(time_point const& time) noexcept { - return static_cast(std::chrono::system_clock::to_time_t(to_sys(std::chrono::time_point_cast(time)))); + return static_cast(std::chrono::system_clock::to_time_t(std::chrono::time_point_cast(to_sys(time)))); } static time_point from_time_t(time_t time) noexcept { - return std::chrono::time_point_cast(from_sys(std::chrono::system_clock::from_time_t(time))); + return from_sys(std::chrono::time_point_cast(std::chrono::system_clock::from_time_t(time))); } static file_time to_file_time(time_point const& time) noexcept diff --git a/strings/base_coroutine_threadpool.h b/strings/base_coroutine_threadpool.h index 415857850..9861b8294 100644 --- a/strings/base_coroutine_threadpool.h +++ b/strings/base_coroutine_threadpool.h @@ -328,6 +328,19 @@ WINRT_EXPORT namespace winrt { } +#if defined(__GNUC__) && !defined(__clang__) + // HACK: GCC seems to require a move when calling operator co_await + // on the return value of resume_after. + // This might be related to upstream bug: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99575 + awaitable(awaitable &&other) noexcept : + m_timer{std::move(other.m_timer)}, + m_duration{std::move(other.m_duration)}, + m_handle{std::move(other.m_handle)}, + m_state{other.m_state.load()} + {} +#endif + void enable_cancellation(cancellable_promise* promise) { promise->set_canceller([](void* context) @@ -434,6 +447,21 @@ WINRT_EXPORT namespace winrt m_handle(handle) {} +#if defined(__GNUC__) && !defined(__clang__) + // HACK: GCC seems to require a move when calling operator co_await + // on the return value of resume_on_signal. + // This might be related to upstream bug: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99575 + awaitable(awaitable &&other) noexcept : + m_wait{std::move(other.m_wait)}, + m_timeout{std::move(other.m_timeout)}, + m_handle{std::move(other.m_handle)}, + m_result{std::move(other.m_result)}, + m_resume{std::move(other.m_resume)}, + m_state{other.m_state.load()} + {} +#endif + void enable_cancellation(cancellable_promise* promise) { promise->set_canceller([](void* context) diff --git a/strings/base_extern.h b/strings/base_extern.h index 52b72bea5..e8a065710 100644 --- a/strings/base_extern.h +++ b/strings/base_extern.h @@ -93,9 +93,15 @@ extern "C" #endif #elif defined(__GNUC__) #if defined(__i386__) -#define WINRT_IMPL_LINK(function, count) __asm__(".weak _WINRT_IMPL_" #function "@" #count "\n.set _WINRT_IMPL_" #function "@" #count ", _" #function "@" #count); +#define WINRT_IMPL_LINK(function, count) __asm__( \ + ".globl _" #function "@" #count "\n\t" \ + ".weak _WINRT_IMPL_" #function "@" #count "\n\t" \ + ".set _WINRT_IMPL_" #function "@" #count ", _" #function "@" #count); #else -#define WINRT_IMPL_LINK(function, count) __asm__(".weak WINRT_IMPL_" #function "\n.set WINRT_IMPL_" #function ", " #function); +#define WINRT_IMPL_LINK(function, count) __asm__( \ + ".globl " #function "\n\t" \ + ".weak WINRT_IMPL_" #function "\n\t" \ + ".set WINRT_IMPL_" #function ", " #function); #endif #endif diff --git a/strings/base_string.h b/strings/base_string.h index 13f3b9dce..2a6023a26 100644 --- a/strings/base_string.h +++ b/strings/base_string.h @@ -488,11 +488,23 @@ namespace winrt::impl T const& object; +#if !defined(__GNUC__) || defined(__clang__) template operator R const& () const noexcept { return reinterpret_cast(object); } +#else + // HACK: GCC does not handle template deduction of const T& conversion + // function according to CWG issue 976. To make this compile on GCC, + // we have to intentionally drop the const qualifier. + // Ref: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61663 + template + operator R& () const noexcept + { + return const_cast(reinterpret_cast(object)); + } +#endif }; template diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6cf1aa3ca..d4bb3e125 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -94,6 +94,16 @@ if(ENABLE_TEST_SANITIZERS) endif() +set(USE_ANSI_COLOR FALSE CACHE BOOL "Enable ANSI colour output for Catch2 test runner.") +if(USE_ANSI_COLOR) + add_compile_definitions(CATCH_CONFIG_COLOUR_ANSI) + set(TEST_COLOR_ARG "--use-colour yes") +endif() + + +set(SKIP_LARGE_PCH FALSE CACHE BOOL "Skip building large precompiled headers.") + + add_subdirectory(test) add_subdirectory(test_cpp20) add_subdirectory(test_win7) diff --git a/test/old_tests/UnitTests/CMakeLists.txt b/test/old_tests/UnitTests/CMakeLists.txt index 7042aa63b..908691653 100644 --- a/test/old_tests/UnitTests/CMakeLists.txt +++ b/test/old_tests/UnitTests/CMakeLists.txt @@ -47,15 +47,17 @@ endforeach() add_executable(test_old Main.cpp ${TEST_SRCS}) target_link_libraries(test_old runtimeobject synchronization) -target_precompile_headers(test_old PRIVATE pch.h) -set_source_files_properties( - conditional_implements_pure.cpp - PROPERTIES SKIP_PRECOMPILE_HEADERS true -) +if(NOT SKIP_LARGE_PCH) + target_precompile_headers(test_old PRIVATE pch.h) + set_source_files_properties( + conditional_implements_pure.cpp + PROPERTIES SKIP_PRECOMPILE_HEADERS true + ) +endif() add_dependencies(test_old build-cppwinrt-projection) add_test( NAME test_old - COMMAND "$" + COMMAND "$" ${TEST_COLOR_ARG} ) diff --git a/test/old_tests/UnitTests/agile_ref.cpp b/test/old_tests/UnitTests/agile_ref.cpp index 2bf6dd637..13f76e17f 100644 --- a/test/old_tests/UnitTests/agile_ref.cpp +++ b/test/old_tests/UnitTests/agile_ref.cpp @@ -39,6 +39,9 @@ IAsyncAction test_agile_ref() #if defined(__clang__) && defined(_MSC_VER) && (defined(_M_IX86) || defined(__i386__)) // FIXME: Test is known to crash from calling invalid address on x86 when built with Clang. TEST_CASE("agile_ref", "[.clang-crash]") +#elif defined(__GNUC__) && !defined(__clang__) +// FIXME: Test is known to randomly crash or abort when built with GCC (segfaults under appverifier). +TEST_CASE("agile_ref", "[.gcc-crash]") #else TEST_CASE("agile_ref") #endif diff --git a/test/old_tests/UnitTests/async.cpp b/test/old_tests/UnitTests/async.cpp index e5c468144..979c2aac7 100644 --- a/test/old_tests/UnitTests/async.cpp +++ b/test/old_tests/UnitTests/async.cpp @@ -1566,10 +1566,12 @@ namespace co_await resume_on_signal(signal); // should not suspend because already signaled REQUIRE(caller == GetCurrentThreadId()); // still on calling thread - REQUIRE(false == co_await resume_on_signal(signal, 1us)); // should suspend but timeout + bool suspend_but_timeout_result = co_await resume_on_signal(signal, 1us); + REQUIRE(false == suspend_but_timeout_result); // should suspend but timeout REQUIRE(caller != GetCurrentThreadId()); // now on background thread - REQUIRE(true == co_await resume_on_signal(signal, 1s)); // should eventually succeed + bool suspend_and_succeed_result = co_await resume_on_signal(signal, 1s); + REQUIRE(true == suspend_and_succeed_result); // should eventually succeed } } diff --git a/test/old_tests/UnitTests/hstring.cpp b/test/old_tests/UnitTests/hstring.cpp index 1369fd229..2abd9769f 100644 --- a/test/old_tests/UnitTests/hstring.cpp +++ b/test/old_tests/UnitTests/hstring.cpp @@ -272,7 +272,6 @@ TEST_CASE("hstring,operator,std::wstring_view") REQUIRE(L"abc" == ws); hs.clear(); - REQUIRE(L"abc" == ws); ws = hs; REQUIRE(ws.empty()); } diff --git a/test/old_tests/UnitTests/weak.cpp b/test/old_tests/UnitTests/weak.cpp index 6724b71ad..55c40dbe6 100644 --- a/test/old_tests/UnitTests/weak.cpp +++ b/test/old_tests/UnitTests/weak.cpp @@ -50,8 +50,8 @@ namespace } }; -// FIXME: Fail to compile with Clang due to incomplete type. -#if !defined(__clang__) +// FIXME: Fail to compile with Clang and GCC due to incomplete type. +#if !defined(__clang__) && !defined(__GNUC__) struct WeakWithSelfReference : implements { winrt::weak_ref weak_self = get_weak(); @@ -447,8 +447,8 @@ TEST_CASE("weak,assignment") // Not constructible from L"" (because Uri constructor is explicit) static_assert(!std::is_constructible_v, const wchar_t*>); -// FIXME: WeakWithSelfReference fails to compile with Clang. -#if !defined(__clang__) +// FIXME: WeakWithSelfReference fails to compile with Clang and GCC. +#if !defined(__clang__) && !defined(__GNUC__) // Constructible from com_ptr because com_ptr is // implicitly convertible to com_ptr. struct Derived : WeakWithSelfReference {}; @@ -487,8 +487,8 @@ TEST_CASE("weak,no_module_lock") REQUIRE(get_module_lock() == object_count); } -// FIXME: WeakWithSelfReference fails to compile with Clang. -#if !defined(__clang__) +// FIXME: WeakWithSelfReference fails to compile with Clang and GCC. +#if !defined(__clang__) && !defined(__GNUC__) TEST_CASE("weak,self") { // The REQUIRE statements are in the WeakWithSelfReference class itself. diff --git a/test/test/CMakeLists.txt b/test/test/CMakeLists.txt index c14fc7c00..ba174d41b 100644 --- a/test/test/CMakeLists.txt +++ b/test/test/CMakeLists.txt @@ -39,6 +39,16 @@ list(APPEND BROKEN_TESTS when ) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # FIXME: GCC does not compile co_await on thread_pool because it wants + # a copy constructor. Disabling this test for now. + # This might be related to upstream bug: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103963 + list(APPEND BROKEN_TESTS + thread_pool + ) +endif() + # Exclude broken tests foreach(TEST_SRCS_EXCLUDE_ITEM IN LISTS BROKEN_TESTS) list(FILTER TEST_SRCS EXCLUDE REGEX "/${TEST_SRCS_EXCLUDE_ITEM}\\.cpp") @@ -63,9 +73,20 @@ set_source_files_properties( PROPERTIES SKIP_PRECOMPILE_HEADERS true ) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC seems to miscompile out_params_bad.cpp if -fdevirtualize is enabled. + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108040 + set_source_files_properties(out_params_bad.cpp PROPERTIES COMPILE_OPTIONS "-fno-devirtualize") + + if(CMAKE_BUILD_TYPE STREQUAL "Release") + # GCC miscompiles multi_threaded_vector.cpp with -O3 + set_source_files_properties(multi_threaded_vector.cpp PROPERTIES COMPILE_OPTIONS "-O2") + endif() +endif() + add_dependencies(test-vanilla build-cppwinrt-projection) add_test( NAME test - COMMAND "$" + COMMAND "$" ${TEST_COLOR_ARG} ) diff --git a/test/test/async_propagate_cancel.cpp b/test/test/async_propagate_cancel.cpp index a3e5af749..b2e331929 100644 --- a/test/test/async_propagate_cancel.cpp +++ b/test/test/async_propagate_cancel.cpp @@ -132,7 +132,8 @@ namespace // FIXME: Test is known to segfault when built with Clang. TEST_CASE("async_propagate_cancel", "[.clang-crash]") #else -TEST_CASE("async_propagate_cancel") +// FIXME: mayfail because of https://github.com/microsoft/cppwinrt/issues/1243 +TEST_CASE("async_propagate_cancel", "[!mayfail]") #endif { Check(Action); diff --git a/test/test/custom_error.cpp b/test/test/custom_error.cpp index f186ee970..8e1855e31 100644 --- a/test/test/custom_error.cpp +++ b/test/test/custom_error.cpp @@ -102,10 +102,16 @@ TEST_CASE("custom_error_logger") // Validate that handler translated exception REQUIRE_THROWS_AS(check_hresult(0x80000018), hresult_illegal_delegate_assignment); REQUIRE(s_loggerCalled); +#ifndef __cpp_lib_source_location // In C++17 these fields cannot be filled in so they are expected to be empty. REQUIRE(s_loggerArgs.lineNumber == 0); REQUIRE(s_loggerArgs.fileName == nullptr); REQUIRE(s_loggerArgs.functionName == nullptr); +#else + // GCC/Clang can only compile these tests in C++20 mode. If source_location + // is available these fields will be filled in. Don't do any checks here + // because these are already tested in `test_cpp20/custom_error.cpp`. +#endif REQUIRE(s_loggerArgs.returnAddress); REQUIRE(s_loggerArgs.result == 0x80000018); // E_ILLEGAL_DELEGATE_ASSIGNMENT) diff --git a/test/test_cpp20/CMakeLists.txt b/test/test_cpp20/CMakeLists.txt index 8b787b696..cbf1b799d 100644 --- a/test/test_cpp20/CMakeLists.txt +++ b/test/test_cpp20/CMakeLists.txt @@ -1,7 +1,10 @@ set(CMAKE_CXX_STANDARD 20) -# std::format, std::ranges::is_heap, std::views::reverse, std::ranges::max -# are experimental in libc++ as of Clang 15. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") +if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # std::format, std::ranges::is_heap, std::views::reverse, std::ranges::max + # are experimental in libc++ as of Clang 15. + # FIXME: Should probably use compile test instead? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") +endif() if(ENABLE_TEST_SANITIZERS) # As of LLVM 15, custom_error.cpp doesn't build with ASAN due to: @@ -39,5 +42,5 @@ add_dependencies(test_cpp20 build-cppwinrt-projection) add_test( NAME test_cpp20 - COMMAND "$" + COMMAND "$" ${TEST_COLOR_ARG} ) diff --git a/test/test_cpp20/custom_error.cpp b/test/test_cpp20/custom_error.cpp index 620ffc755..707088cd9 100644 --- a/test/test_cpp20/custom_error.cpp +++ b/test/test_cpp20/custom_error.cpp @@ -59,7 +59,11 @@ TEST_CASE("custom_error_logger") REQUIRE(fileNameSv.find("custom_error.cpp") != std::string::npos); const auto functionNameSv = std::string_view(s_loggerArgs.functionName); REQUIRE(!functionNameSv.empty()); +#if defined(__GNUC__) && !defined(__clang__) + REQUIRE(functionNameSv == "void {anonymous}::FailOnLine15()"); +#else REQUIRE(functionNameSv == "FailOnLine15"); +#endif REQUIRE(s_loggerArgs.returnAddress); REQUIRE(s_loggerArgs.result == 0x80000018); // E_ILLEGAL_DELEGATE_ASSIGNMENT) diff --git a/test/test_win7/CMakeLists.txt b/test/test_win7/CMakeLists.txt index 7ada247c4..d1c4120ea 100644 --- a/test/test_win7/CMakeLists.txt +++ b/test/test_win7/CMakeLists.txt @@ -32,6 +32,16 @@ list(APPEND BROKEN_TESTS when ) +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # FIXME: GCC does not compile co_await on thread_pool because it wants + # a copy constructor. Disabling this test for now. + # This might be related to upstream bug: + # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=103963 + list(APPEND BROKEN_TESTS + thread_pool + ) +endif() + # Exclude broken tests foreach(TEST_SRCS_EXCLUDE_ITEM IN LISTS BROKEN_TESTS) list(FILTER TEST_SRCS EXCLUDE REGEX "/${TEST_SRCS_EXCLUDE_ITEM}\\.cpp") @@ -54,5 +64,5 @@ add_dependencies(test_win7 build-cppwinrt-projection) add_test( NAME test_win7 - COMMAND "$" + COMMAND "$" ${TEST_COLOR_ARG} )