[fix] Fix backslash normalization in VSTestCLIRunSettings on Unix
Fixes #15043
Root Cause
VSTestCLIRunSettings was declared as string[] in ITestTask, VSTestTask, and VSTestTask2. When MSBuild processes a string[] task parameter, it wraps each element in an ITaskItem. On Unix, ITaskItem.ItemSpec normalizes path separators, silently converting \ to /.
This corrupted any run setting containing backslash characters ā such as regex patterns passed via:
dotnet test -- NUnit.Where='namespace =~ /Abc\.Space1($|\.)/'
The \. sequences became //, causing the regex to fail.
Fix
Changed VSTestCLIRunSettings from string[] to string in ITestTask, VSTestTask, and VSTestTask2. This bypasses ITaskItem creation entirely, so no path normalization occurs. In TestTaskUtils.CreateCommandLineArguments, the string is now split manually by newlines and semicolons (maintaining backward compatibility with both separators).
Tests
- Updated existing
CreateArgumentShouldAddOneEntryForCLIRunSettingsandCreateArgumentShouldAddCLIRunSettingsArgAtEndtests to use the newstringtype with newline separator. - Added
CreateArgumentShouldPreserveBackslashesInCLIRunSettingsthat verifies backslashes in CLI run settings are not converted to forward slashes. - Added
VSTestCLIRunSettingsMustBindAsStringToSurviveUnixPathNormalization, which asserts the parameter stays astringon both tasks. That is the regression worth guarding against and it runs on every platform.
Why CI was red
RunDotnetTestWithCLIRunSettingsContainingBackslashes failed on Linux and macOS with Namespace//.Class//b. That doubled slash is what ITaskItem.ItemSpec produces, so the inner dotnet test was still running the old string[] binding even though this branch declares the property as string. I checked the binding on Linux with a small MSBuild project that takes the same value as both a string and a string[] parameter:
| Parameter type | Value the task received |
|---|---|
string | TestRunParameters.Parameter(name=\"pattern\",;value=\"Namespace\\.Class\\b\") |
string[] | TestRunParameters.Parameter(name=/"pattern/", value=/"Namespace//.Class//b/") |
The string[] row is exactly what CI reported, so the fix itself is correct and the test was simply not loading the task we build. It now passes /p:VSTestTaskAssemblyFile to point the VSTest targets at the task from our own package instead of whatever Microsoft.TestPlatform.Build.dll the SDK ships, and it checks that file exists first so a wrong path fails with a clear message instead of quietly falling back.
š¤ Fix submitted by Issue Repro Triage & Auto-Fix š
š Triaged by Issue Repro Triage & Auto-Fix š
š¤