Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSVC support #35

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open

MSVC support #35

wants to merge 5 commits into from

Conversation

mikedld
Copy link
Contributor

@mikedld mikedld commented Mar 31, 2018

The changes here are based in part on #29 for which I thank @uael. This is a minimal set of changes needed to make use of ctest with recent versions of MSVC, without any warnings even on /W4.

Despite the fact that I have omitted Travis/AppVeyor stuff here (compared to #29), I do second the need in some kind of CI to make sure all the supported platforms are in fact supported.

Tested on Windows/MSVC, Linux/GCC, and Mac/Clang.

Where GCC and Clang issue warnings such as "empty struct is a GNU extension"
(with `-Wgnu-empty-struct`) or "empty struct has size 0 in C, size 1 in C++"
(with `-Wc++-compat`), MSVC issues an error C2016 "C requires that a struct
or union has at least one member".
This fixes MSVC warning C4013 "'type cast': conversion from 'unsigned int'
to 'void *' of greater size".
On Windows, `Sleep` is used. It's not as precise as `usleep`: it accepts
milliseconds instead of microseconds, and even then its precision is
limited to about 16ms. This is a test code though so it doesn't matter
much.
Since we're already using `vsnprintf` and other C99 features, it's only
natural to use `snprintf` as well. Moreover, `sprintf` is "deprecated" in
Microsoft's CRT in favor of more secure (Microsoft-specific) alternatives
with its use resulting in a warning, while `snprintf` isn't (although
requires recent enough CRT).
This doesn't include changes to support `SIGSEGV` as even its support in
its current form seems questionable to me. This means that to use ctest
with MSVC you need to leave `CTEST_SIGSEGV` undefined.
@bvdberg
Copy link
Owner

bvdberg commented Apr 4, 2018

I've merged some commits, but not all:

  • eliminate type casting warning: 2 casts in a row seem a bit ugly. It 's only the example code,
    so we could change this to some other arbritrary example
  • sleep: seems a bit too much to add so much code in the examples. We could also remove the
    usleep() altogether since it has no real function
  • segfault: i dont entirely understand the win issues

@@ -79,14 +79,19 @@ CTEST_IMPL_DIAG_POP()
#define CTEST_IMPL_TEARDOWN_FPNAME(sname) CTEST_IMPL_NAME(sname##_teardown_ptr)

#define CTEST_IMPL_MAGIC (0xdeadbeef)
#ifdef __APPLE__
#if defined(_MSC_VER)
Copy link
Contributor

@aceckel aceckel Apr 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know the minimum version of MSVC that these changes work for? Can we check for it in the code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO there is no point in checking for a particular version since MSVC only started properly supporting C99 (think designated initializers and snprintf) quite recently (with VS 2013/2015).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so you are saying that, since the code assumes C99 features are present, any MSVC version that supports C99 will work. If we ever decide to support C89 compilation though, we may want to revisit this.

#pragma data_seg(push)
#pragma data_seg(".ctest$u")
#pragma data_seg(pop)
#define CTEST_IMPL_SECTION __declspec(allocate(".ctest$u")) __declspec(align(1))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: we can combine these __declspecs into one:

__declspec(allocate(".ctest$u"), align(1))

Apparently some compilers even will complain if you don't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While works with MSVC, this comma-separated format is not documented AFAICT. MSDN article on __declspec only suggests that space-separated attributes are supported, and so does Clang test case on __declspec. I'd rather not change anything here at all, and silence corresponding Borland's warning/error (which is quite stupid if you ask me) if it comes to that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally reasonable, I'm fine with leaving it as-is.

#ifdef __APPLE__
#if defined(_MSC_VER)
#pragma data_seg(push)
#pragma data_seg(".ctest$u")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the significance of $u here? I cannot find any documentation online about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copied blindly from #29. I agree that "subsection" is not really needed in this case, will remove upon rebase.

#pragma data_seg(".ctest$u")
#pragma data_seg(pop)
#define CTEST_IMPL_SECTION __declspec(allocate(".ctest$u")) __declspec(align(1))
#elif defined(__APPLE__)
#define CTEST_IMPL_SECTION __attribute__ ((used, section ("__DATA, .ctest"), aligned(1)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the gcc/clang definitions of CTEST_IMPL_SECTION, we use the attribute "used" to prevent an optimizing compiler from stripping the symbol from the resulting object file for never being referenced in the code. Does MSVC provide an analogous __declspec or #pragma for this sort of thing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find anything like used right away. Also don't recall whether I tested the optimized build or not, will need to get back to you on this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A simple and portable workaround for the symbol-stripping problem would be to remove static from the definitions. Compilers cannot strip non-static symbols even if they appear unused since another compilation unit may be externing them. This would also remove the need for the used attribute in the __GNUC__ case.

@mikedld
Copy link
Contributor Author

mikedld commented Apr 7, 2018

@bvdberg I agree on both points regarding the example code. If you would change the cases with casts yourself that would be great.

As for the SIGSEGV support, there's no issue with it on Windows. The problem is though that printf, fflush etc. are not safe functions to call from a signal handler (see MSDN on signal, POSIX.1-2017 on signal actions) and so the whole idea of handling SIGSEGV the way ctest does (on any platform, not only Windows) is moot to me.

@FrancescAlted
Copy link

Hi. We have been using ctest (nice library!) on a Mac box without problems, but because master does not currently compile on Windows, we have been trying out this PR. After applying it, it compiles flawlessly on VS 2017; however, it does not detect any test from the suite:

C:\Users\faltet\ctest\build>Debug\test_example.exe
�[0;32mRESULTS: 0 tests (0 ok, 0 failed, 0 skipped) ran in 0 ms�[0m

NOTE: some tests will fail, just to show how ctest works! ;)

As you see, no test is detected. After some debugging, we see that the culprit is the https://github.com/bvdberg/ctest/blob/master/ctest.h#L475 line, that breaks immediately, so not increasing the ctest_end variable. Any hint on what we could be missing?

@mikedld
Copy link
Contributor Author

mikedld commented Nov 22, 2018

@FrancescAlted, thanks for testing this out.

As I mentioned in one of the comments above, I didn't try using these changes on optimized builds. Is that something you're doing? And if so, could you try it on a debug build instead?

Also, both loops there (including the one above one you're pointing to) need to break right away for ctest to be unable to find any tests. Early break in just one of them is totally fine.

@FrancescAlted
Copy link

Hi Mike. Thanks for the response. Yes, we were trying a debug build (I did not try a release one yet).

And yes, I confirm that both loops break right away, so this is the reason why no tests are detected. Using VS 2017 and Windows 10 here.

@aceckel
Copy link
Contributor

aceckel commented Nov 22, 2018

@FrancescAlted Could you please provide a minimal code example exhibiting the problem, along with the compile and link commands used to build?

@FrancescAlted
Copy link

I am using the test_example.c in this repo. It may well be that we did not properly merge this PR (the original ctest master already has merged some of the commits in the PR, so we had to do the merge manually).

Below is a transcript of how we build and run the test_example.c:

C:\Users\faltet\ctest\build>cmake ..
-- Building for: Visual Studio 15 2017
-- The C compiler identification is MSVC 19.16.27023.1
-- The CXX compiler identification is MSVC 19.16.27023.1
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working CXX compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
CMake Warning (dev) in CMakeLists.txt:
  No cmake_minimum_required command is present.  A line of code such as

    cmake_minimum_required(VERSION 3.13)

  should be added at the top of the file.  The version specified may be lower
  if you wish to support older CMake versions for this project.  For more
  information run "cmake --help-policy CMP0000".
This warning is for project developers.  Use -Wno-dev to suppress it.

-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/faltet/ctest/build

C:\Users\faltet\ctest\build>cmake --build .
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 22/11/2018 11:19:41.
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" on node 1 (default targets).
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj
" (2) on node 1 (default targets).
InitializeBuildStatus:
  Creating "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Checking Build System
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj" (default targets).

Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\test_example.vcxpr
oj" (3) on node 1 (default targets).
InitializeBuildStatus:
  Creating "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
ClCompile:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\CL.exe /c /Zi
   /nologo /W3 /WX- /diagnostics:classic /Od /Ob0 /Oy- /D WIN32 /D _WINDOWS /D "CMAKE_INTDIR=\"Debug\"" /D _MBCS /Gm- /
  RTC1 /MDd /GS /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"test_example.dir\Debug\\" /Fd"test_example.dir\Debug\vc141.pdb"
   /Gd /TC /analyze- /errorReport:queue C:\Users\faltet\ctest\main.c C:\Users\faltet\ctest\test_example.c
  main.c
  test_example.c
  Generating Code...
Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\link.exe /ERR
  ORREPORT:QUEUE /OUT:"C:\Users\faltet\ctest\build\Debug\test_example.exe" /INCREMENTAL /NOLOGO kernel32.lib user32.lib
   gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:
  "level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG /PDB:"C:/Users/faltet/ctest/build/Debug/test_example.pdb"
   /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Users/faltet/ctest/build/Debug/test_example.lib" /MAC
  HINE:X86 /SAFESEH  /machine:X86 test_example.dir\Debug\main.obj
  test_example.dir\Debug\test_example.obj
  test_example.vcxproj -> C:\Users\faltet\ctest\build\Debug\test_example.exe
FinalizeBuildStatus:
  Deleting file "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild".
  Touching "test_example.dir\Debug\test_example.tlog\test_example.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\test_example.vcxproj" (default targets).

InitializeBuildStatus:
  Creating "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\ALL_BUILD.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.96

C:\Users\faltet\ctest\build>Debug\test_example.exe
�[0;32mRESULTS: 0 tests (0 ok, 0 failed, 0 skipped) ran in 0 ms�[0m

NOTE: some tests will fail, just to show how ctest works! ;)

C:\Users\faltet\ctest\build>

Thanks in advance for any hint you may provide.

@mikedld
Copy link
Contributor Author

mikedld commented Nov 23, 2018

The culprit seems to be with debugging information generation. CMake passes /Zi compiler flag and /debug + /INCREMENTAL linker flags by default for "Debug" and "RelWithDebInfo" configurations, and I was always testing without those.

This worked for me:

cmake_minimum_required(VERSION 3.0)
project(ctest C)

foreach(T DEBUG RELWITHDEBINFO)
    string(REPLACE "/Zi" "" CMAKE_C_FLAGS_${T} "${CMAKE_C_FLAGS_${T}}")
    string(REPLACE "/debug" "" CMAKE_EXE_LINKER_FLAGS_${T} "${CMAKE_EXE_LINKER_FLAGS_${T}}")
    string(REPLACE "/INCREMENTAL" "" CMAKE_EXE_LINKER_FLAGS_${T} "${CMAKE_EXE_LINKER_FLAGS_${T}}")
endforeach()

add_executable(mytests main.c mytests.c)

We'll need to dig into this, but the workaround above looks sensible in the meantime (you could adjust the flags for test projects only and see if that works).

@FrancescAlted
Copy link

I have had a try at your CMakeLists file, but no luck. I have updated it slightly so as to use the existing test_example.c in our repo:

cmake_minimum_required(VERSION 3.0)
project(ctest C)

foreach(T DEBUG RELWITHDEBINFO)
    string(REPLACE "/Zi" "" CMAKE_C_FLAGS_${T} "${CMAKE_C_FLAGS_${T}}")
    string(REPLACE "/debug" "" CMAKE_EXE_LINKER_FLAGS_${T} "${CMAKE_EXE_LINKER_FLAGS_${T}}")
    string(REPLACE "/INCREMENTAL" "" CMAKE_EXE_LINKER_FLAGS_${T} "${CMAKE_EXE_LINKER_FLAGS_${T}}")
endforeach()

add_executable(test_example main.c test_example.c)

And the output with this new cmake file is:

C:\Users\faltet\ctest\build>cmake ..
-- Building for: Visual Studio 15 2017
-- The C compiler identification is MSVC 19.16.27023.1
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/faltet/ctest/build

C:\Users\faltet\ctest\build>cmake --build .
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 23/11/2018 12:11:51.
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" on node 1 (default targets).
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj
" (2) on node 1 (default targets).
InitializeBuildStatus:
  Creating "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Checking Build System
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj" (default targets).

Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\test_example.vcxpr
oj" (3) on node 1 (default targets).
InitializeBuildStatus:
  Creating "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
ClCompile:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\CL.exe /c /no
  logo /W3 /WX- /diagnostics:classic /Od /Ob0 /Oy- /D WIN32 /D _WINDOWS /D "CMAKE_INTDIR=\"Debug\"" /D _MBCS /Gm- /RTC1
   /MDd /GS /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"test_example.dir\Debug\\" /Fd"test_example.dir\Debug\vc141.pdb" /Gd
   /TC /analyze- /errorReport:queue C:\Users\faltet\ctest\main.c C:\Users\faltet\ctest\test_example.c
  main.c
  test_example.c
  Generating Code...
Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\link.exe /ERR
  ORREPORT:QUEUE /OUT:"C:\Users\faltet\ctest\build\Debug\test_example.exe" /INCREMENTAL /NOLOGO kernel32.lib user32.lib
   gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTUAC:
  "level='asInvoker' uiAccess='false'" /manifest:embed /PDB:"C:/Users/faltet/ctest/build/Debug/test_example.pdb" /SUBSY
  STEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Users/faltet/ctest/build/Debug/test_example.lib" /MACHINE:X8
  6 /SAFESEH  /machine:X86 test_example.dir\Debug\main.obj
  test_example.dir\Debug\test_example.obj
  test_example.vcxproj -> C:\Users\faltet\ctest\build\Debug\test_example.exe
FinalizeBuildStatus:
  Deleting file "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild".
  Touching "test_example.dir\Debug\test_example.tlog\test_example.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\test_example.vcxproj" (default targets).

InitializeBuildStatus:
  Creating "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\ALL_BUILD.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.83

C:\Users\faltet\ctest\build>Debug\test_example.exe
�[0;32mRESULTS: 0 tests (0 ok, 0 failed, 0 skipped) ran in 0 ms�[0m

NOTE: some tests will fail, just to show how ctest works! ;)

C:\Users\faltet\ctest\build>

@mikedld
Copy link
Contributor Author

mikedld commented Nov 23, 2018

I still see /INCREMENTAL flag passed to linker. Not sure why that is the case, the difference is that I'm using "NMake Makefiles" generator since I don't have Visual Studio installed but only Build Tools.

@FrancescAlted
Copy link

Urgh, this seems fragile indeed. It is unfortunate that macros are so little portable among different compilers. Thanks for the hint anyway; we will spend a bit more time on this and see.

@mikedld
Copy link
Contributor Author

mikedld commented Nov 23, 2018

After fiddling with it a bit more it became clear that the incremental linking is in fact the real problem. Try this and see if it works for you:

cmake_minimum_required(VERSION 3.0)
project(ctest C)

add_executable(mytests main.c mytests.c)
set_property(TARGET mytests APPEND_STRING PROPERTY LINK_FLAGS " /INCREMENTAL:NO")

Note that instead of removing presumably present /INCREMENTAL flag I'm instead adding /INCREMENTAL:NO which then comes after the former one in the command line (have no idea how reliable this is) thus overriding it. From ${BuildDir}/CMakeFiles/mytests.dir/build.make:

mytests.exe: CMakeFiles\mytests.dir\main.c.obj
mytests.exe: CMakeFiles\mytests.dir\mytests.c.obj
mytests.exe: CMakeFiles\mytests.dir\build.make
mytests.exe: CMakeFiles\mytests.dir\objects1.rsp
	@$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=C:\Repo\ctest\obj\CMakeFiles --progress-num=$(CMAKE_PROGRESS_3) "Linking C executable mytests.exe"
	"C:\Program Files\CMake\bin\cmake.exe" -E vs_link_exe --intdir=CMakeFiles\mytests.dir --manifests  -- C:\PROGRA~2\MICROS~1\2017\BUILDT~1\VC\Tools\MSVC\1415~1.267\bin\Hostx86\x86\link.exe /nologo @CMakeFiles\mytests.dir\objects1.rsp @<<
 /out:mytests.exe /implib:mytests.lib /pdb:C:\Repo\ctest\obj\mytests.pdb /version:0.0  /machine:X86 /debug /INCREMENTAL /subsystem:console  /INCREMENTAL:NO kernel32.lib user32.lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib 
<<

@FrancescAlted
Copy link

Oh yeah, /INCREMENTAL:NO works indeed for us.

Just for reference, here it is a log on the new behaviour:

C:\Users\faltet\ctest\build>cmake ..
-- Building for: Visual Studio 15 2017
-- The C compiler identification is MSVC 19.16.27023.1
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe
-- Check for working C compiler: C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/bin/Hostx86/x86/cl.exe -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: C:/Users/faltet/ctest/build

C:\Users\faltet\ctest\build>cmake --build .
Microsoft (R) Build Engine version 15.9.20+g88f5fadfbe for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.

Build started 23/11/2018 18:05:10.
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" on node 1 (default targets).
Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj
" (2) on node 1 (default targets).
InitializeBuildStatus:
  Creating "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Checking Build System
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ZERO_CHECK\ZERO_CHECK.tlog\ZERO_CHECK.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ZERO_CHECK.vcxproj" (default targets).

Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (1) is building "C:\Users\faltet\ctest\build\test_example.vcxpr
oj" (3) on node 1 (default targets).
InitializeBuildStatus:
  Creating "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
ClCompile:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\CL.exe /c /Zi
   /nologo /W3 /WX- /diagnostics:classic /Od /Ob0 /Oy- /D WIN32 /D _WINDOWS /D "CMAKE_INTDIR=\"Debug\"" /D _MBCS /Gm- /
  RTC1 /MDd /GS /Zc:wchar_t /Zc:forScope /Zc:inline /Fo"test_example.dir\Debug\\" /Fd"test_example.dir\Debug\vc141.pdb"
   /Gd /TC /analyze- /errorReport:queue C:\Users\faltet\ctest\main.c C:\Users\faltet\ctest\test_example.c
  main.c
  test_example.c
  Generating Code...
Link:
  C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\bin\HostX86\x86\link.exe /ERR
  ORREPORT:QUEUE /OUT:"C:\Users\faltet\ctest\build\Debug\test_example.exe" /INCREMENTAL:NO /NOLOGO kernel32.lib user32.
  lib gdi32.lib winspool.lib shell32.lib ole32.lib oleaut32.lib uuid.lib comdlg32.lib advapi32.lib /MANIFEST /MANIFESTU
  AC:"level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG /PDB:"C:/Users/faltet/ctest/build/Debug/test_example.p
  db" /SUBSYSTEM:CONSOLE /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"C:/Users/faltet/ctest/build/Debug/test_example.lib" /
  MACHINE:X86 /SAFESEH  /machine:X86 test_example.dir\Debug\main.obj
  test_example.dir\Debug\test_example.obj
  test_example.vcxproj -> C:\Users\faltet\ctest\build\Debug\test_example.exe
FinalizeBuildStatus:
  Deleting file "test_example.dir\Debug\test_example.tlog\unsuccessfulbuild".
  Touching "test_example.dir\Debug\test_example.tlog\test_example.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\test_example.vcxproj" (default targets).

InitializeBuildStatus:
  Creating "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild" because "AlwaysCreate" was specified.
CustomBuild:
  Building Custom Rule C:/Users/faltet/ctest/CMakeLists.txt
  CMake does not need to re-run because C:/Users/faltet/ctest/build/CMakeFiles/generate.stamp is up-to-date.
FinalizeBuildStatus:
  Deleting file "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\unsuccessfulbuild".
  Touching "Win32\Debug\ALL_BUILD\ALL_BUILD.tlog\ALL_BUILD.lastbuildstate".
Done Building Project "C:\Users\faltet\ctest\build\ALL_BUILD.vcxproj" (default targets).


Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.99

C:\Users\faltet\ctest\build>Debug\test_example.exe
TEST 1/31 suite1:test1 �[01;32m[OK]�[0m
TEST 2/31 suite1:test2 �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:10  expected 1, got 2�[0m
TEST 3/31 suite2:test1 �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:14  expected 'foo', got 'bar'�[0m
TEST 4/31 suite3:test3 �[01;32m[OK]�[0m
TEST 5/31 memtest:test1 �[01;32m[OK]�[0m
�[0;34m  LOG: ctest_memtest_setup() data=00BD9DD0 buffer=00000000�[0m
�[0;34m  LOG: ctest_memtest_test1_run()  data=00BD9DD0  buffer=013B3B20�[0m
�[0;34m  LOG: ctest_memtest_teardown() data=00BD9DD0 buffer=013B3B20�[0m
TEST 6/31 memtest:test3 �[01;33m[SKIPPED]�[0m
TEST 7/31 memtest:test2 �[01;31m[FAIL]�[0m
�[0;34m  LOG: ctest_memtest_setup() data=00BD9DD8 buffer=00000000�[0m
�[0;34m  LOG: ctest_memtest_test2_run()  data=00BD9DD8  buffer=013B3B20�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:51  shouldn't come here�[0m
TEST 8/31 fail:test1 �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:62  shouldn't come here�[0m
TEST 9/31 weaklinkage:test1 �[01;32m[OK]�[0m
�[0;34m  LOG: ctest_weaklinkage_test1_run()�[0m
TEST 10/31 weaklinkage:test2 �[01;32m[OK]�[0m
�[0;34m  LOG: ctest_weaklinkage_test2_run()�[0m
TEST 11/31 nosetup:test1 �[01;32m[OK]�[0m
�[0;34m  LOG: ctest_nosetup_test1_run()�[0m
�[0;34m  LOG: ctest_nosetup_teardown()�[0m
TEST 12/31 ctest:test_assert_str �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:105  expected 'foo', got 'bar'�[0m
TEST 13/31 ctest:test_assert_equal �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:110  expected 123, got 456�[0m
TEST 14/31 ctest:test_assert_not_equal �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:115  should not be 123�[0m
TEST 15/31 ctest:test_assert_interval �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:120  expected 1000-2000, got 3000�[0m
TEST 16/31 ctest:test_assert_null �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:125  should be NULL�[0m
TEST 17/31 ctest:test_assert_not_null_const �[01;32m[OK]�[0m
TEST 18/31 ctest:test_assert_not_null �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:134  should not be NULL�[0m
TEST 19/31 ctest:test_assert_true �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:139  should be true�[0m
TEST 20/31 ctest:test_assert_false �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:144  should be false�[0m
TEST 21/31 ctest:test_skip �[01;33m[SKIPPED]�[0m
TEST 22/31 ctest:test_assert_fail �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:152  shouldn't come here�[0m
TEST 23/31 ctest:test_null_null �[01;32m[OK]�[0m
TEST 24/31 ctest:test_null_string �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:161  expected '(null)', got 'shouldfail'�[0m
TEST 25/31 ctest:test_string_null �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:165  expected 'shouldfail', got '(null)'�[0m
TEST 26/31 ctest:test_string_diff_ptrs �[01;32m[OK]�[0m
TEST 27/31 ctest:test_large_numbers �[01;32m[OK]�[0m
TEST 28/31 ctest:test_ctest_err �[01;31m[FAIL]�[0m
�[0;33m  ERR: error log�[0m
TEST 29/31 ctest:test_dbl_near �[01;32m[OK]�[0m
TEST 30/31 ctest:test_dbl_near_tol �[01;31m[FAIL]�[0m
�[0;33m  ERR: C:\Users\faltet\ctest\test_example.c:190  expected 1.000e-04, got 1.110e-04 (diff -1.100e-05, tol 1.000e-05)�[0m
TEST 31/31 ctest:test_dbl_far �[01;32m[OK]�[0m
�[01;31mRESULTS: 31 tests (12 ok, 17 failed, 2 skipped) ran in 80 ms�[0m

NOTE: some tests will fail, just to show how ctest works! ;)

C:\Users\faltet\ctest\build>

Oh man, the black magic of compiler flags. Anyway, thanks a lot!

@aceckel
Copy link
Contributor

aceckel commented Nov 23, 2018

@mikedld In light of this discussion on compiler flags, perhaps we should replace the Makefile in this repo with a CMakeLists.txt that has special handling for the MSVC case?

@mikedld
Copy link
Contributor Author

mikedld commented Nov 25, 2018

@aceckel, I'm not strictly against it, but adding a section to README.md is okay with me too. Swithing to CMake may also seem a bit unfriendly since it's quite a requirement (well, not really if you think about it...) for a small project like this.

I'd like to investigate this further though and maybe come up with a solution that works regardless of compiler/linker flags.

@aceckel
Copy link
Contributor

aceckel commented Nov 27, 2018

@mikedld So I think the issue with incremental linking is that the linker inserts zero-padding around definitions, so we cannot rely on struct ctest objects being contiguous in the .ctest section. We can handle this by constructing a list of struct ctest objects from the .ctest section at runtime by comparing magics. Something like this (untested)?

#pragma section(".ctest$a")
#pragma section(".ctest$u")
#pragma section(".ctest$z")

...

static const struct ctest __declspec(allocate(".ctest$a")) ctest_section_begin;
static const struct ctest __declspec(allocate(".ctest$z")) ctest_section_end;

...

    struct ctest* test = &ctest_section_begin + 1;
    while (test <= &ctest_section_end - 1) {
        if (test->magic == CTEST_IMPL_MAGIC) {
            ctest_list_add(test);
            ++test;
        } else {
            test = (struct ctest *)(((uint8_t *)test) + __alignof(struct ctest));
        }
    }

Not sure if __alignof is available in visual C or just visual C++ though, but we can do something else if not.

Another option would be to add a static constructor to the the windows CRT (with __declspec(allocate(.CRT$XCU))) for each struct ctest object which adds the object to a global list. That would probably be more efficient since we would not need to compare magics. It also would remove the need for a magic altogether.

@aceckel
Copy link
Contributor

aceckel commented Nov 27, 2018

That might look something like this (also untested):

#define CTEST_IMPL_CTOR_FNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_ctor)
#define CTEST_IMPL_CTOR_FPNAME(sname, tname) CTEST_IMPL_NAME(sname##_##tname##_ctor_ptr)

...

#define CTEST_IMPL_CTEST(sname, tname, tskip) \
    static void CTEST_IMPL_FNAME(sname, tname)(void); \
    CTEST_IMPL_STRUCT(sname, tname, tskip, NULL, NULL, NULL); \
    static void CTEST_IMPL_CTOR_FNAME(sname, tname)(void) \
    { \
        ctest_list_add(&CTEST_IMPL_TNAME(sname, tname)); \
    } \
    __declspec(allocate(.CRT$XCU)) static void (*CTEST_IMPL_CTOR_FPNAME(sname, tname))(void) = CTEST_IMPL_CTOR_FNAME(sname, tname); \
    static void CTEST_IMPL_FNAME(sname, tname)(void)

@aceckel
Copy link
Contributor

aceckel commented Dec 10, 2018

@mikedld I've confirmed that the above approach works correctly on MSVC 2017 with @FrancescAlted's original configuration. Additionally, I tried it with several combinations of optimization flags (including link-time optimization) and wasn't able to break it. Should I open a pull request to mikedld:msvc-support with these changes?

@mikedld
Copy link
Contributor Author

mikedld commented Dec 10, 2018

@aceckel Greatly appreciate you working on this. Linked list approach was something I had in mind when posting the last time. Using constructors is a nice trick but I'm a bit worried as to its support when non-MS linker/CRT is used on Windows in that we'll have to support two mechanisms for test cases discovery... I know that at least GCC and Clang have support for similar .ctors section too, but I also feel uneasy when reading phrases such as the following (re. C++), which may not be that horrible but I have no idea:

To obtain a list of those dynamic initializers run the command dumpbin /all main.obj, and then search the .CRT$XCU section (when main.cpp is compiled as a C++ file, not a C file).

In any case, I'll need to rebase this branch first before you make any PRs to it. Will find time for it next couple of days.

@bvdberg Anything you'd like to add here? If not for the recent discussion, maybe you have an idea on what to replace those sleep calls with? :)

@bvdberg
Copy link
Owner

bvdberg commented Dec 16, 2018

I never develop/work under any MS platform. Better for my bloodpressure ;)

@aceckel
Copy link
Contributor

aceckel commented Dec 19, 2018

[...] I'm a bit worried [...] that we'll have to support two mechanisms for test cases discovery... I know that at least GCC and Clang have support for similar .ctors section too [...]

Instead of supporting multiple mechanisms for test discovery, we can use the linked-list approach for all platforms. For GNU-compatible compilers, we can use the __attribute__((constructor)) extension. I prefer this over placing function pointers in the .ctors section directly via __attribute__((section(".ctors"))).

[...] I'm a bit worried as to its support when non-MS linker/CRT is used on Windows [...]

What do you mean by "non-MS on Windows" here? If you mean Cygwin or MinGW, I believe the __attribute__((constructor)) method will work for those platforms since their respective compilers support GNU C extensions.

[...] I also feel uneasy when reading phrases such as the following (re. C++), which may not be that horrible but I have no idea:

To obtain a list of those dynamic initializers run the command dumpbin /all main.obj, and then search the .CRT$XCU section (when main.cpp is compiled as a C++ file, not a C file).

I believe the documentation tells the reader to compile as a C++ file and not a C file because the example code snippet they give to demonstrate CRT initialization is valid C++ but not valid C (a "initializer is not a constant" error occurs when compiling as a C file). AFAICT, the function pointers in .CRT$XCU are called during CRT initialization for both C and C++ programs.

[...] [M]aybe you have an idea on what to replace those sleep calls with?

It looks like the sleep calls were removed in master so I don't think we need to worry about this incompatibility anymore.

In any case, I'll need to rebase this branch first before you make any PRs to it. Will find time for it next couple of days.

Sounds good @mikedld, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants