{"id":5904,"date":"2024-05-06T07:00:56","date_gmt":"2024-05-06T14:00:56","guid":{"rendered":"http:\/\/writeasync.net\/?p=5904"},"modified":"2024-05-05T23:19:23","modified_gmt":"2024-05-06T06:19:23","slug":"testing-static_assert-msbuild","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5904","title":{"rendered":"Testing static_assert (MSBuild)"},"content":{"rendered":"<p>Now that we&#8217;ve gotten a handle on <a href=\"http:\/\/writeasync.net\/?p=5901\">testing static_assert for CMake<\/a>, let&#8217;s turn our focus to <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/msbuild\/msbuild?view=vs-2022\">MSBuild<\/a>. We will use a nearly identical project structure as what was established in the <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync-cpp\/tree\/main\/src\/stassert\">CMake <code>stassert<\/code> sample project<\/a> but with all the of the, uh, &#8220;beauty&#8221; of <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/build\/reference\/vcxproj-file-structure?view=msvc-170\">.vcxproj<\/a> and <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/extensibility\/internals\/solution-dot-sln-file?view=vs-2022\">.sln<\/a> files. (For good measure, we have also switched to <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/test\/writing-unit-tests-for-c-cpp?view=vs-2022\">VSTest for C++ unit tests<\/a>.) Here is the result: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync\/commit\/4e8cb5a0823766b15eca185e4bf451a819abf9bb\">StaticAssertSample &#8211; initial port from CMake<\/a><\/p>\n<p>As before with CMake, we now need to cobble together a compiler command that would test each <code>static_assert<\/code> based on a conditional compilation symbol, for example:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\n#ifdef STASSERT_NAME_HAS_WRONG_TYPE\r\n        struct WrongType\r\n        {\r\n            std::string name{};\r\n        };\r\n\r\n        TEST_METHOD(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>It turns out that, in this department, we are in luck &#8212; sort of. During a C++ project build, the compiler commands are indeed recorded, though mainly for the benefit of <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/msbuild\/incremental-builds?view=vs-2022\">incremental build<\/a>. The only drawback, as clearly laid out in the documentation for <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/extensibility\/visual-cpp-project-extensibility?view=vs-2022#tlog-files\">.tlog files<\/a> (the underlying file format for tracking build inputs and outputs) is that command-line .tlog file contents are not specifically documented and &#8220;determined by the MSBuild task that produces them.&#8221;<\/p>\n<p>However, it is pretty clear that the format is nearly identical to that of the &#8220;read&#8221; and &#8220;write&#8221; .tlog files which <em>are<\/em> documented and presumably stable. A typical command-line file might look like this (example adapted from the <code>CL.command.1.tlog<\/code> for the StaticAssertTest project):<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n^D:\\SOME\\PATH\\PROJECTS\\STATICASSERTSAMPLE\\TEST\\EXAMPLE_TEST.CPP\r\n\/c \/I..\\INC \/I&quot;C:\\PROGRAM FILES\\MICROSOFT VISUAL STUDIO\\2022\\ENTERPRISE\\VC\\UNITTEST\\INCLUDE&quot; \/I&quot;D:\\OTHER\\PATH\\CPP\\VCPKG\\INSTALLED\\X64-WINDOWS\\INCLUDE&quot; \/ZI \/JMC \/nologo \/W3 \/WX- \/diagnostics:column \/sdl \/Od \/D _DEBUG \/D _WINDLL \/D _UNICODE \/D UNICODE \/Gm- \/EHsc \/RTC1 \/MDd \/GS \/fp:precise \/Zc:wchar_t \/Zc:forScope \/Zc:inline \/std:c++20 \/Fo&quot;X64\\DEBUG\\\\&quot; \/Fd&quot;X64\\DEBUG\\VC143.PDB&quot; \/external:W3 \/Gd \/TP \/FC D:\\SOME\\PATH\\PROJECTS\\STATICASSERTSAMPLE\\TEST\\EXAMPLE_TEST.CPP\r\n<\/pre>\n<p>The first line is a caret (^) followed by the full path the .cpp source file. The second line is the full argument list for CL.exe. While it may not be contractual, it seems simple enough to throw together a prototype where we assume this format. So let&#8217;s press on!<\/p>\n<p>At the end of our test project, we can add some custom items and an associated targets import:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n  &lt;ItemGroup&gt;\r\n    &lt;StaticAssertTest Include=&quot;STASSERT_NAME_HAS_WRONG_TYPE&quot;&gt;\r\n      &lt;TestFile&gt;example_test.cpp&lt;\/TestFile&gt;\r\n      &lt;Pattern&gt;Type must have 'name' field of type `const char\\*`&lt;\/Pattern&gt;\r\n    &lt;\/StaticAssertTest&gt;\r\n    &lt;StaticAssertTest Include=&quot;STASSERT_NAME_HAS_WRONG_SIZE&quot;&gt;\r\n      &lt;TestFile&gt;example_test.cpp&lt;\/TestFile&gt;\r\n      &lt;Pattern&gt;Type must not have size of 16&lt;\/Pattern&gt;\r\n    &lt;\/StaticAssertTest&gt;\r\n  &lt;\/ItemGroup&gt;\r\n  &lt;Import Project=&quot;StaticAssertTest.targets&quot; \/&gt;\r\n<\/pre>\n<p>The items lay out a series of tests that we want to perform. The <code>Include<\/code> (<a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/msbuild\/msbuild-well-known-item-metadata?view=vs-2022\">the &#8220;identity&#8221; metadata component<\/a>) is the <code>#define<\/code> symbol; exactly one of these defines a unique test. The metadata items <code>TestFile<\/code> and <code>Pattern<\/code> specify the related information for the test &#8212; which source file it is associated with and the expected <code>static_assert<\/code> error pattern, respectively. Now we need our custom .targets file:<\/p>\n<pre class=\"brush: xml; title: ; notranslate\" title=\"\">\r\n&lt;Project&gt;\r\n  &lt;Target Name=&quot;StaticAssertTest&quot; AfterTargets=&quot;ClCompile&quot;&gt;\r\n    &lt;ItemGroup&gt;\r\n      &lt;CLCommandFile Include=&quot;$(TLogLocation)CL.command.*.tlog&quot; \/&gt;\r\n      &lt;StaticAssertTestCommand Include=&quot;@(StaticAssertTest)&quot;&gt;\r\n        &lt;Args&gt;-Symbol &quot;%(Identity)&quot; -Pattern &quot;%(Pattern)&quot; -TestFile &quot;%(TestFile)&quot;&lt;\/Args&gt;\r\n      &lt;\/StaticAssertTestCommand&gt;\r\n    &lt;\/ItemGroup&gt;\r\n    &lt;Exec Command=&quot;powershell.exe -File Test-StaticAssert.ps1 -ClExe &amp;quot;$(ClCompilerPath)&amp;quot; %(StaticAssertTestCommand.Args) -CommandFile &amp;quot;@(CLCommandFile-&gt;'%(fullpath)', '&amp;quot;,&amp;quot;')&amp;quot;&quot; \/&gt;\r\n  &lt;\/Target&gt;\r\n&lt;\/Project&gt;\r\n<\/pre>\n<p>This file defines one StaticAssertTest target which runs after the <a href=\"https:\/\/learn.microsoft.com\/en-us\/cpp\/build\/reference\/msbuild-visual-cpp-overview?view=msvc-170#targets\">C++ compiler (ClCompile) target<\/a>. It uses an <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/msbuild\/msbuild-items?view=vs-2022#use-wildcards-to-specify-items\">item wildcard<\/a> to select all the possible .tlog command files and another item to build up the script arguments we will need to run the test. We then simply hand off to <code>powershell.exe<\/code> to run our <code>Test-StaticAssert.ps1<\/code> script to do the real work:<\/p>\n<pre class=\"brush: powershell; title: ; notranslate\" title=\"\">\r\n&#x5B;CmdletBinding()]\r\nparam(\r\n    &#x5B;Parameter(Mandatory=$true)]\r\n    &#x5B;string]$ClExe,\r\n    &#x5B;Parameter(Mandatory=$true)]\r\n    &#x5B;string]$Symbol,\r\n    &#x5B;Parameter(Mandatory=$true)]\r\n    &#x5B;string]$Pattern,\r\n    &#x5B;Parameter(Mandatory=$true)]\r\n    &#x5B;string&#x5B;]]$CommandFile,\r\n    &#x5B;Parameter(Mandatory=$true)]\r\n    &#x5B;string]$TestFile\r\n)\r\n\r\nFunction Exit-Task($errorText) {\r\n    Write-Host &quot;$($PSCommandPath): error : $errorText&quot;\r\n    Exit 1\r\n}\r\n\r\n$clArgs = ''\r\n$outPath = ''\r\nforeach ($file in $CommandFile) {\r\n    if (!(Test-Path $file)) {\r\n        Exit-Task &quot;Command file '$file' not found&quot;\r\n    }\r\n\r\n    $lines = Get-Content $file\r\n    # First line has the following format:\r\n    # ^X:\\SOME\\FULL\\PATH\\FILE.CPP\r\n    $cppFile = Split-Path ($lines&#x5B;0].Substring(1)) -Leaf\r\n    if ($cppFile -eq $TestFile) {\r\n        # Second line has actual args to CL.exe\r\n        $clArgs = $lines&#x5B;1]\r\n        break\r\n    }\r\n}\r\n\r\nif (!$clArgs) {\r\n    Exit-Task &quot;Could not find CL args for test file '$TestFile'&quot;\r\n}\r\n\r\n$clArgs += &quot; \/D$Symbol&quot;\r\n$cmdText = &quot;`&quot;$ClExe`&quot; $clArgs&quot;\r\nWrite-Host $cmdText\r\n# The CL.exe path (and possibly some arguments) will be quoted. This causes\r\n# trouble for invoking the expression directly, so invoke via cmd.exe.\r\n$output = &amp; cmd.exe \/c $cmdText\r\nif ($LASTEXITCODE -eq 0) {\r\n    Exit-Task &quot;Compilation unexpectedly succeeded for '$Symbol'&quot;\r\n}\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    $output | Write-Host\r\n    Exit-Task &quot;Did not find output line for '$Symbol' matching '$Pattern'&quot;\r\n}\r\n<\/pre>\n<p>The script is not too different from the one we built for CMake, though with some MSBuild adaptations. For one, it tries to produce an <a href=\"https:\/\/learn.microsoft.com\/en-us\/visualstudio\/msbuild\/msbuild-diagnostic-format-for-tasks?view=vs-2022\">error output that would be understood better by MSBuild<\/a> which helps highlight the actual problem when something goes wrong. Also, since these command lines can have paths with spaces, there is a lot of argument quoting needed. This is <a href=\"https:\/\/stackoverflow.com\/questions\/1673967\/how-to-run-an-exe-file-in-powershell-with-parameters-with-spaces-and-quotes\">somewhat of a minefield in PowerShell<\/a> but I found success by just passing off to a <a href=\"https:\/\/stackoverflow.com\/questions\/515309\/what-does-cmd-c-mean\">&#8220;cmd.exe \/c&#8221; expression<\/a> via the <a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/scripting\/learn\/shell\/running-commands?view=powershell-7.4#powershell-commands-that-run-other-commands\">call operator (<code>&amp;<\/code>)<\/a>.<\/p>\n<p>The end result is a set of post-build <code>static_assert<\/code> &#8220;tests&#8221; that run equally well on the msbuild.exe command line or in the Visual Studio IDE. A failing test has an output like the following:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&quot;D:\\some\\path\\projects\\StaticAssertSample\\StaticAssertSample.sln&quot; (default target) (1) -&gt;\r\n&quot;D:\\some\\path\\projects\\StaticAssertSample\\test\\StaticAssertTest.vcxproj&quot; (default target) (4) -&gt;\r\n(StaticAssertTest target) -&gt;\r\n  D:\\some\\path\\projects\\StaticAssertSample\\INC\\example.h(13,33): error C2338: static_assert failed: 'Type must not have size of 16' &#x5B;D:\\some\\path\\projects\\StaticAssertSample\\test\\StaticAssertTest.vcxproj]\r\n  D:\\some\\path\\projects\\StaticAssertSample\\test\\Test-StaticAssert.ps1 : error : Did not find output line for 'STASSERT_NAME_HAS_WRONG_SIZE' matching 'Type must not have size of 99' &#x5B;D:\\some\\path\\projects\r\n\\StaticAssertSample\\test\\StaticAssertTest.vcxproj]\r\n  D:\\some\\path\\projects\\StaticAssertSample\\test\\StaticAssertTest.targets(9,5): error MSB3073: The command &quot;powershell.exe -File Test-StaticAssert.ps1 -ClExe &quot;C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.39.33519\\bin\\HostX64\\x64\\cl.exe&quot; -Symbol &quot;STASSERT_NAME_HAS_WRONG_SIZE&quot; -Pattern &quot;Type must not have size of 16&quot; -TestFile &quot;example_test.cpp&quot; -CommandFile &quot;some\\path\\projects\\StaticAssertSample\\test\\x64\\Debug\\StaticAssertTest.tlog\\CL.command.1.tlog&quot;&quot; exited with code 1. &#x5B;D:\\some\\path\\projects\\StaticAssertSample\\test\\StaticAssertTest.vcxproj]\r\n<\/pre>\n<p>Yes, it&#8217;s somewhat lengthy, but the error condition is front and center.<\/p>\n<p>If you have a need for <code>static_assert<\/code> validation <em>and<\/em> you use Visual Studio with .vcxproj files, this might be a good starting point (no promises, though!). All of the code for this sample can be found here: <a href=\"https:\/\/github.com\/brian-dot-net\/writeasync\/tree\/master\/projects\/StaticAssertSample\">StaticAssertSample on GitHub<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Now that we&#8217;ve gotten a handle on testing static_assert for CMake, let&#8217;s turn our focus to MSBuild. We will use a nearly identical project structure as what was established in the CMake stassert sample project but with all the of&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],"tags":[],"class_list":["post-5904","post","type-post","status-publish","format-standard","hentry","category-build","category-native"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5904","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5904"}],"version-history":[{"count":3,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5904\/revisions"}],"predecessor-version":[{"id":5907,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5904\/revisions\/5907"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5904"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5904"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5904"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}