{"id":5901,"date":"2024-04-29T07:00:16","date_gmt":"2024-04-29T14:00:16","guid":{"rendered":"http:\/\/writeasync.net\/?p=5901"},"modified":"2024-04-28T22:40:03","modified_gmt":"2024-04-29T05:40:03","slug":"testing-static_assert-cmake","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5901","title":{"rendered":"Testing static_assert (CMake)"},"content":{"rendered":"<p>One of the many innovations in C++11 was <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/static_assert\">static_assert<\/a>. This allowed, at long last, a custom error message at compile time. Sure, we&#8217;ve had the <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/preprocessor\/hash-error-directive-c-cpp?view=msvc-170\"><code>#error<\/code> directive<\/a> for a while, but that only works for conditions that one could evaluate at preprocessing time. On the contrary, <code>static_assert<\/code> can (with enough <a href=\"https:\/\/en.wikipedia.org\/wiki\/Template_metaprogramming\">metaprogramming<\/a>) see anything the compiler can see. Here is an <a href=\"https:\/\/github.com\/microsoft\/wil\/blob\/963263543179642aa69addd13697a609c7b838d9\/include\/wil\/registry.h#L978\">example from WIL<\/a> where <code>static_assert<\/code> provides a clear, unambiguous error (instead of an <a href=\"https:\/\/www.reddit.com\/r\/cpp\/comments\/xn09hn\/why_are_template_errors_so_horrendously_verbose\/\">inscrutable template error<\/a>):<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ Blocking get_value_string template types that are not already specialized - this gives a much friendlier compiler error message\r\ntemplate &lt;typename T&gt;\r\nT get_value_string(HKEY \/*key*\/, _In_opt_ PCWSTR \/*subkey*\/, _In_opt_ PCWSTR \/*value_name*\/)\r\n{\r\n    static_assert(sizeof(T) != sizeof(T), &quot;Unsupported type for get_value_string&quot;);\r\n}\r\n<\/pre>\n<p>Yes, <code>static_assert<\/code> is a great feature &#8212; one which may help us get ever-closer to the ideal of <a href=\"https:\/\/fsharpforfunandprofit.com\/posts\/designing-with-types-making-illegal-states-unrepresentable\/\">making illegal states unrepresentable<\/a>. This is especially valuable for library development; we would much prefer that a misuse of a feature simply fail to compile rather than <a href=\"https:\/\/r6.ca\/blog\/20040530T203100Z.html\">blow up at runtime<\/a>.<\/p>\n<p>Ah, but there is the conundrum. With runtime errors, we can easily write unit tests to <a href=\"https:\/\/google.github.io\/googletest\/reference\/assertions.html#exceptions\">assert the error behavior<\/a>. But <code>static_assert<\/code> generates <em>compiler<\/em> errors; how are we supposed to test for those?<\/p>\n<p>As it turns out, <a href=\"https:\/\/stackoverflow.com\/questions\/35278945\/verify-static-assert-in-a-unit-test\">this question<\/a> <a href=\"https:\/\/stackoverflow.com\/questions\/17408824\/how-to-write-runnable-tests-of-static-assert\">has come up<\/a> <a href=\"https:\/\/www.reddit.com\/r\/cpp\/comments\/bisbeh\/unit_test_v_static_assert\/\">more than a few times<\/a>. There is even a related cottage industry of sorts on how to achieve <a href=\"http:\/\/softwarephilosophy.ninja\/compile-time-unit-testing\">unit testing<\/a> at <a href=\"https:\/\/www.reddit.com\/r\/cpp\/comments\/1c4kk4f\/c20_compiletime_first_macro_free_unittesting\/\">compile time<\/a>. But let&#8217;s not stray too far from our goal here &#8212; what we would like is for a <code>static_assert<\/code> in our code to be explicitly tested just like any other feature.<\/p>\n<p>By far the best treatment of this subject is by <a href=\"https:\/\/github.com\/rbock\">Dr. Roland Bock<\/a> who gave the CppCon 2016 talk <a href=\"https:\/\/www.youtube.com\/watch?v=wTmAJAk7WV4\">&#8220;How to test static_assert.&#8221;<\/a> His solution is somewhat involved but works quite well for his use case in his <a href=\"https:\/\/github.com\/rbock\/sqlpp11\">sqlpp11<\/a> library. In short, he recommends a particular formulation of <code>static_assert<\/code> which provides predictable cross-platform behavior at the cost of a bit template magic (see for yourself in <a href=\"https:\/\/github.com\/rbock\/sqlpp11\/blob\/b92a9a765687245657472a47fa63f7c0b2455c92\/include\/sqlpp11\/portable_static_assert.h#L34\">portable_static_assert.h<\/a>). This is a great solution, but I actually want to explore the option he rejected at the front of his talk &#8212; I want to test my <code>static_assert<\/code>s with the compiler but in a way that fits better with my unit tests.<\/p>\n<p>Right off the bat, we need to be clear that any &#8220;test&#8221; of <code>static_assert<\/code> is going to involve invoking the compiler. In particular, we need to provide the compiler with a program that should <em>fail<\/em> compilation, and that failure is <em>success<\/em> for the <code>static_assert<\/code> test. Further, we want to know that the failure is really the one we expect; it is pretty easy to write a program that fails for any one of a million reasons but not <em>the<\/em> reason that we care about.<\/p>\n<p>Let&#8217;s start with a contrived example of a static assertion &#8212; maybe not so useful in real life but good enough for demonstration purposes:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstd::string get_name(const char* raw);\r\n\r\ntemplate &lt;typename T&gt;\r\nclass Example\r\n{\r\n    static_assert(std::is_same_v&lt;decltype(T::name), const char*&gt;, &quot;Type must have 'name' field of type `const char*`&quot;);\r\n\r\npublic:\r\n    Example(const T&amp; input) : m_name{ get_name(input.name) }\r\n    {}\r\n\r\n    std::string operator()() const\r\n    {\r\n        return &quot;Hello, &quot; + m_name + &quot;!&quot;;\r\n    }\r\n\r\nprivate:\r\n    std::string m_name;\r\n};\r\n<\/pre>\n<p>In this imaginary library, we have a template class <code>Example<\/code> which assumes that the type <code>T<\/code> has a <code>const char* name<\/code> field. Regardless of the assertion, the code <em>would<\/em> fail to compile if we break this assumption (when trying to evaluate the <code>input.name<\/code> expression). But let&#8217;s say we just like our custom error message better (<code>\"Type must have 'name' field of type `const char*`\"<\/code>). (And yes, we could use <a href=\"https:\/\/www.cppstories.com\/2021\/concepts-intro\/\">C++20 concepts<\/a> for this, but bear with me here.)<\/p>\n<p>Here are some unit tests for this type, which are limited to testing runtime behavior only:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n\/\/ stassert_test.cpp\r\n\/\/ ...\r\n\r\nstruct MyVal\r\n{\r\n    const char* name{};\r\n};\r\n\r\nTEST(example_test, has_name)\r\n{\r\n    MyVal val{ .name = &quot;world&quot; };\r\n    Example&lt;MyVal&gt; hello{val};\r\n\r\n    ASSERT_EQ(&quot;Hello, world!&quot;, hello());\r\n}\r\n\r\nTEST(example_test, has_null_name)\r\n{\r\n    MyVal val{};\r\n    Example&lt;MyVal&gt; hello{ val };\r\n\r\n    ASSERT_EQ(&quot;Hello, &lt;null&gt;!&quot;, hello());\r\n}\r\n<\/pre>\n<p>What would be nice is if I could add the following test and somehow validate that it fails while compiling:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct WrongType\r\n{\r\n    std::string name{};\r\n};\r\n\r\nTEST(example_test, name_has_wrong_type)\r\n{\r\n    WrongType val{};\r\n\r\n    Example&lt;WrongType&gt; hello{ val };\r\n}\r\n<\/pre>\n<p>Of course, if I add this code, my entire test suite will fail to compile (for <code>static_assert<\/code> reasons). To get anywhere with this approach, I need <a href=\"https:\/\/gcc.gnu.org\/onlinedocs\/cpp\/Conditionals.html\">conditional compilation<\/a>. Let&#8217;s enlist the help of the <a href=\"https:\/\/www.codewithc.com\/demystifying-the-preprocessor-its-role-in-c-programming\/\">much-maligned preprocessor<\/a>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifdef STASSERT_NAME_HAS_WRONG_TYPE\r\nstruct WrongType\r\n{\r\n    std::string name{};\r\n};\r\n\r\nTEST(example_test, name_has_wrong_type)\r\n{\r\n    WrongType val{};\r\n\r\n    Example&lt;WrongType&gt; hello{ val };\r\n}\r\n#endif\r\n<\/pre>\n<p>Now I can have the rest of the test suite compile while hiding this code section, as long as no one has defined <code>STASSERT_NAME_HAS_WRONG_TYPE<\/code>. The next bit of machinery we need is some sort of compiler invocation that <code>#define<\/code>s this symbol and spits out the expected error message. CMake actually does have a <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/command\/try_compile.html\">try_compile<\/a> feature that seems like it could be useful here; in my exploration of it, though, I could not get it to work in a simple enough way to recommend it.<\/p>\n<p>Instead, let&#8217;s try to capture the compiler command that we already used to build our test suite. This is actually a feature of CMake, enabled with <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/variable\/CMAKE_EXPORT_COMPILE_COMMANDS.html\">CMAKE_EXPORT_COMPILE_COMMANDS<\/a>:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n# put this at the top of the relevant CMakeLists.txt file\r\nset(CMAKE_EXPORT_COMPILE_COMMANDS true)\r\n<\/pre>\n<p>All the relevant commands would thus be saved in a <code>compile_commands.json<\/code> file in the <code>CMAKE_BINARY_DIR<\/code>. But we need some other process to interpret the command and invoke the modified build step to trigger our <code>static_assert<\/code>. We&#8217;re going to need JSON parsing, text wrangling, and handling an external process &#8212; clearly a job for an advanced scripting system. If we want to be relatively cross-platform, we can write a <a href=\"https:\/\/devblogs.microsoft.com\/powershell\/getting-started-with-powershell-core-on-windows-mac-and-linux\/\">PowerShell Core<\/a> script-based cmdlet:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n# Test-StaticAssert.ps1\r\n&#x5B;CmdletBinding()]\r\nparam(\r\n\t&#x5B;Parameter(Mandatory=$true)]\r\n\t&#x5B;string]$Symbol,\r\n\t&#x5B;Parameter(Mandatory=$true)]\r\n\t&#x5B;string]$Pattern,\r\n\t&#x5B;Parameter(Mandatory=$true)]\r\n\t&#x5B;string]$WorkingDirectory,\r\n\t&#x5B;Parameter(Mandatory=$false)]\r\n\t&#x5B;string]$CompileCommands = 'compile_commands.json',\r\n\t&#x5B;Parameter(Mandatory=$false)]\r\n\t&#x5B;string]$TestFile = 'stassert_test.cpp'\r\n)\r\n\r\nif (!(Test-Path $WorkingDirectory)) {\r\n\tthrow &quot;Working directory '$WorkingDirectory' not found&quot;\r\n}\r\n\r\nPush-Location $WorkingDirectory\r\nWrite-Host &quot;Set working directory '$WorkingDirectory'&quot;\r\n\r\nif (!(Test-Path $CompileCommands)) {\r\n    throw &quot;Compile commands file '$CompileCommands' not found&quot;\r\n}\r\n\r\n$item = Get-Content $CompileCommands |\r\n    ConvertFrom-Json | Where-Object { $_.file -like &quot;*$TestFile&quot;}\r\n\r\nif (!$item) {\r\n    throw &quot;Could not find compile command for test file '$TestFile'&quot;\r\n}\r\n\r\n$command = $item.command -replace '@\\S+\\.modmap ', ''\r\n$command += &quot; \/D$Symbol&quot;\r\nWrite-Host &quot;Invoking compilation command: $command&quot;\r\n$output = Invoke-Expression $command\r\nif ($LASTEXITCODE -eq 0) {\r\n    throw &quot;Compilation unexpectedly succeeded for '$Symbol'&quot;\r\n}\r\n\r\nWrite-Host &quot;Output: $output&quot;\r\n\r\n$matched = $false\r\n$output | Select-String -Pattern $Pattern | ForEach-Object {\r\n    $matched = $true\r\n    Write-Host &quot;Found matching output line for '$Symbol': $_&quot;\r\n}\r\n\r\nif (!$matched) {\r\n    throw &quot;Did not find output line for '$Symbol' matching '$Pattern'&quot;\r\n}\r\n<\/pre>\n<p>The script here reads the relevant command from the .json file, executes a modified version of it (filtering out some sort of <a href=\"https:\/\/www.kitware.com\/import-cmake-c20-modules\/\">module map<\/a> response file that caused me trouble in practice), and tries to match the output with an expected pattern. Importantly, it fails if the compile is successful (remember, we expect a compilation error!) or if there is no matching output message.<\/p>\n<p>The last step is to incorporate this &#8220;test&#8221; into the CMake build. Here is one way:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n# our normal test suite\r\nadd_executable(stassert-test\r\n    &quot;test\/stassert_test.cpp&quot;\r\n)\r\n\r\n# . . .\r\n# our static_assert test infra\r\nfunction(add_stassert_test symbol err_pattern)\r\n    set(test_script &quot;${CMAKE_CURRENT_SOURCE_DIR}\/test\/Test-StaticAssert.ps1&quot;)\r\n    string(CONCAT test_script_args\r\n        &quot; -Symbol ${symbol}&quot;\r\n        &quot; -Pattern \\&quot;${err_pattern}\\&quot;&quot;\r\n        &quot; -WorkingDirectory ${CMAKE_BINARY_DIR}&quot;\r\n    )\r\n    add_custom_command(TARGET stassert-test POST_BUILD\r\n        COMMAND pwsh ${test_script} ${test_script_args}\r\n    )\r\nendfunction()\r\n\r\n# the first static_assert test!\r\nadd_stassert_test(STASSERT_NAME_HAS_WRONG_TYPE &quot;Type must have 'name' field of type `const char\\\\*`&quot;)\r\n<\/pre>\n<p>We are using <a href=\"https:\/\/cmake.org\/cmake\/help\/latest\/command\/add_custom_command.html\">add_custom_command<\/a> to incorporate a post-build step for the test executable. The command simply invokes the script above based on a preprocessor symbol and the error pattern we expect.<\/p>\n<p>Amazingly, this actually works, as shown in the CMake build output:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&#x5B;47\/48] Linking CXX executable src\\stassert\\stassert-test.exe\r\nSet working directory '...\/writeasync-cpp\/out\/build\/x64-release'\r\nInvoking compilation command: ...\\VC\\Tools\\MSVC\\1439~1.335\\bin\\Hostx64\\x64\\cl.exe  \/nologo \/TP -DGTEST_LINKED_AS_SHARED_LIBRARY=1 -I...\\writeasync-cpp\\src\\stassert\\inc -external:I...\\writeasync-cpp\\out\\build\\x64-release\\vcpkg_installed\\x64-windows\\include -external:W0 \/DWIN32 \/D_WINDOWS \/GR \/EHsc \/O2 \/Ob2 \/DNDEBUG -std:c++20 -MD \/W4 -WX \/Fosrc\\stassert\\CMakeFiles\\stassert-test.dir\\test\\stassert_test.cpp.obj \/FdTARGET_COMPILE_PDB \/FS -c ...\\writeasync-cpp\\src\\stassert\\test\\stassert_test.cpp \/DSTASSERT_NAME_HAS_WRONG_TYPE\r\nOutput: stassert_test.cpp ...\\writeasync-cpp\\src\\stassert\\inc\\example.h(13): error C2338: static_assert failed: 'Type must have 'name' field of type `const char*`' ...\r\nFound matching output line for 'STASSERT_NAME_HAS_WRONG_TYPE': ...\\writeasync-cpp\\src\\stassert\\inc\\example.h(13): error C2338: static_assert failed: 'Type must have 'name' field of type `const char*`'\r\n<\/pre>\n<p>Most importantly, if we &#8220;break&#8221; the <code>static_assert<\/code>, say, by erroneously allowing this code to compile, we get a clear error:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n  Exception: ...\\writeasync-cpp\\src\\stassert\\test\\Test-StaticAssert.ps1:38\r\n  Line |\r\n    38 |      throw &quot;Compilation unexpectedly succeeded for '$Symbol'&quot;\r\n       |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n  \r\n       | Compilation unexpectedly succeeded for 'STASSERT_NAME_HAS_WRONG_TYPE'\r\n<\/pre>\n<p>In fact, we can even use TDD now for <code>static_assert<\/code>! Let&#8217;s say we want to also assert that we cannot handle objects of size 16. First, add a new test at the end of the CMake file with the expected symbol and error:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nadd_stassert_test(STASSERT_NAME_HAS_WRONG_SIZE &quot;Type must not have size of 16&quot;)\r\n<\/pre>\n<p>This should fail the build step (as expected) because we haven&#8217;t added any of this code yet, and the test program would still compile. Now, let&#8217;s add the test program code:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifdef STASSERT_NAME_HAS_WRONG_SIZE\r\nstruct WrongSize\r\n{\r\n    const char* name{};\r\n    const char* extra{};\r\n};\r\n\r\nTEST(example_test, name_has_wrong_size)\r\n{\r\n    WrongSize val{};\r\n\r\n    Example&lt;WrongSize&gt; hello{ val };\r\n}\r\n#endif\r\n<\/pre>\n<p>We are still in a failing state because the program as of now still compiles. Finally, we add the <code>static_assert<\/code>:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\ntemplate &lt;typename T&gt;\r\nclass Example\r\n{\r\n    static_assert(std::is_same_v&lt;decltype(T::name), const char*&gt;, &quot;Type must have 'name' field of type `const char*`&quot;);\r\n    \/\/ new assert follows:\r\n    static_assert(sizeof(T) != 16, &quot;Type must not have size of 16&quot;);\r\n\/\/ ...\r\n<\/pre>\n<p>At this point the build succeeds, which means our <code>static_assert<\/code> works (i.e., fails the build) as expected. Check out the GitHub project with all of the associated code here: <a href=\"writeasync-cpp\/src\n\/stassert\/\">https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/tree\/main\/src\/stassert<\/a><\/p>\n<p>One could certainly find many issues with this approach. For one, it is relatively heavyweight. A program with hundreds of <code>static_assert<\/code> statements would need hundreds of compiler invocations to be fully tested. But this is simply the reality &#8212; the only true test of <code>static_assert<\/code> is indeed a program that provably fails the assert. Perhaps it is not great to harvest build commands from CMake &#8212; maybe <code>try_compile<\/code> has better odds of working in a fully cross-platform way. It&#8217;s a cool hack, but is it practical?<\/p>\n<p>Nevertheless, I thought this was an interesting jumping off point to TDD your asserts. What do you think?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>One of the many innovations in C++11 was static_assert. This allowed, at long last, a custom error message at compile time. Sure, we&#8217;ve had the #error directive for a while, but that only works for conditions that one could evaluate&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[108,101,41,51],"tags":[],"class_list":["post-5901","post","type-post","status-publish","format-standard","hentry","category-build","category-native","category-tdd","category-testing"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5901","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5901"}],"version-history":[{"count":2,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5901\/revisions"}],"predecessor-version":[{"id":5903,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5901\/revisions\/5903"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5901"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5901"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}