mirror of https://github.com/microsoft/WSL
Resolve issue with config file writing sections outside of their expected header. (#13898)
* Resolve issue with config file writing sections outside of their expected header. * add more writewslconfig variations * formatting --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
This commit is contained in:
parent
9ed5c130f4
commit
180d811099
|
|
@ -66,12 +66,30 @@ Build parameters:
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### Unit Tests (Windows Only - TAEF Framework)
|
### Unit Tests (Windows Only - TAEF Framework)
|
||||||
|
|
||||||
|
**CRITICAL: ALWAYS build the ENTIRE project before running tests:**
|
||||||
|
```powershell
|
||||||
|
# Build everything first - this is required!
|
||||||
|
cmake --build . -- -m
|
||||||
|
|
||||||
|
# Then run tests
|
||||||
|
bin\<platform>\<target>\test.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why full build is required:**
|
||||||
|
- Tests depend on multiple components (libwsl.dll, wsltests.dll, wslservice.exe, etc.)
|
||||||
|
- Partial builds (e.g., only `configfile` or `wsltests`) will cause test failures
|
||||||
|
- Changed components must be built together to ensure compatibility
|
||||||
|
- **DO NOT skip the full build step even if only one file changed**
|
||||||
|
|
||||||
|
Test execution:
|
||||||
- Run all tests: `bin\<platform>\<target>\test.bat`
|
- Run all tests: `bin\<platform>\<target>\test.bat`
|
||||||
- **NEVER CANCEL: Full test suite takes 30-60 minutes. Set timeout to 90+ minutes.**
|
- **NEVER CANCEL: Full test suite takes 30-60 minutes. Set timeout to 90+ minutes.**
|
||||||
- Run subset: `bin\<platform>\<target>\test.bat /name:*UnitTest*`
|
- Run subset: `bin\<platform>\<target>\test.bat /name:*UnitTest*`
|
||||||
- Run specific test: `bin\<platform>\<target>\test.bat /name:<class>::<test>`
|
- Run specific test: `bin\<platform>\<target>\test.bat /name:<class>::<test>`
|
||||||
- WSL1 tests: Add `-Version 1` flag
|
- WSL1 tests: Add `-Version 1` flag
|
||||||
- Fast mode (after first run): Add `-f` flag (requires `wsl --set-default test_distro`)
|
- Fast mode (after first run): Add `-f` flag (requires `wsl --set-default test_distro`)
|
||||||
|
- **Requires Administrator privileges** - test.bat will fail without admin rights
|
||||||
|
|
||||||
Test debugging:
|
Test debugging:
|
||||||
- Wait for debugger: `/waitfordebugger`
|
- Wait for debugger: `/waitfordebugger`
|
||||||
|
|
|
||||||
|
|
@ -240,7 +240,6 @@ int ParseConfigFile(std::vector<ConfigKey>& keys, FILE* file, int flags, const w
|
||||||
{
|
{
|
||||||
wint_t ch = 0;
|
wint_t ch = 0;
|
||||||
unsigned long line = 0;
|
unsigned long line = 0;
|
||||||
size_t newKeyValueInsertPos = 0;
|
|
||||||
bool trailingComment = false;
|
bool trailingComment = false;
|
||||||
bool inQuote = false;
|
bool inQuote = false;
|
||||||
size_t trimmedLength = 0;
|
size_t trimmedLength = 0;
|
||||||
|
|
@ -328,8 +327,6 @@ NewLine:
|
||||||
|
|
||||||
if (trailingComment)
|
if (trailingComment)
|
||||||
{
|
{
|
||||||
// Subtract 1 to account for ch being '\n' or WEOF.
|
|
||||||
newKeyValueInsertPos = configFileOutput.length() - 1;
|
|
||||||
trailingComment = false;
|
trailingComment = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -390,6 +387,37 @@ NewLine:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '[':
|
case '[':
|
||||||
|
// We're about to parse a new section. If we have an unwritten key-value
|
||||||
|
// and the current section matches, write it now before moving to the new section.
|
||||||
|
if (updateConfigFile && !outputKeyValueUpdated && !removeKey && sectionLength > 0)
|
||||||
|
{
|
||||||
|
const auto& outputConfigKey = outputKey.value();
|
||||||
|
if (outputConfigKey.Matches(key.c_str(), sectionLength))
|
||||||
|
{
|
||||||
|
const auto& keyNames = outputConfigKey.GetNames();
|
||||||
|
// Config key without name.
|
||||||
|
FAIL_FAST_IF(keyNames.empty());
|
||||||
|
const auto keyNameUtf8 = keyNames.front();
|
||||||
|
const auto keyName = wsl::shared::string::MultiByteToWide(keyNameUtf8);
|
||||||
|
const auto sectionKeySeparatorPos = keyName.find('.');
|
||||||
|
// Config key without separated section/key name
|
||||||
|
FAIL_FAST_IF(sectionKeySeparatorPos == std::string_view::npos);
|
||||||
|
// Config key without section name
|
||||||
|
FAIL_FAST_IF(sectionKeySeparatorPos == 0);
|
||||||
|
// Config key without key name
|
||||||
|
FAIL_FAST_IF(sectionKeySeparatorPos == (keyName.length() - 1));
|
||||||
|
|
||||||
|
// Remove any trailing newlines before inserting the new key-value
|
||||||
|
while (!configFileOutput.empty() && configFileOutput.back() == L'\n')
|
||||||
|
{
|
||||||
|
configFileOutput.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto keyValue = std::format(L"\n{}={}\n\n", keyName.substr(sectionKeySeparatorPos + 1), outputKey.value().GetValue());
|
||||||
|
configFileOutput += keyValue;
|
||||||
|
outputKeyValueUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
goto ParseSection;
|
goto ParseSection;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -418,30 +446,6 @@ NewLine:
|
||||||
}
|
}
|
||||||
|
|
||||||
ParseSection:
|
ParseSection:
|
||||||
if (updateConfigFile && !outputKeyValueUpdated && !removeKey && sectionLength > 0)
|
|
||||||
{
|
|
||||||
const auto& outputConfigKey = outputKey.value();
|
|
||||||
if (outputConfigKey.Matches(key.c_str(), sectionLength))
|
|
||||||
{
|
|
||||||
const auto& keyNames = outputConfigKey.GetNames();
|
|
||||||
// Config key without name.
|
|
||||||
FAIL_FAST_IF(keyNames.empty());
|
|
||||||
const auto keyNameUtf8 = keyNames.front();
|
|
||||||
const auto keyName = wsl::shared::string::MultiByteToWide(keyNameUtf8);
|
|
||||||
const auto sectionKeySeparatorPos = keyName.find('.');
|
|
||||||
// Config key without separated section/key name
|
|
||||||
FAIL_FAST_IF(sectionKeySeparatorPos == std::string_view::npos);
|
|
||||||
// Config key without section name
|
|
||||||
FAIL_FAST_IF(sectionKeySeparatorPos == 0);
|
|
||||||
// Config key without key name
|
|
||||||
FAIL_FAST_IF(sectionKeySeparatorPos == (keyName.length() - 1));
|
|
||||||
|
|
||||||
auto keyValue = std::format(L"\n{}={}", keyName.substr(sectionKeySeparatorPos + 1), outputKey.value().GetValue());
|
|
||||||
configFileOutput.insert(newKeyValueInsertPos, keyValue);
|
|
||||||
outputKeyValueUpdated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse [section] ([ is already parsed)
|
// parse [section] ([ is already parsed)
|
||||||
if (updateConfigFile)
|
if (updateConfigFile)
|
||||||
{
|
{
|
||||||
|
|
@ -796,7 +800,6 @@ ValueDone:
|
||||||
// Trim any trailing space.
|
// Trim any trailing space.
|
||||||
value.resize(trimmedLength);
|
value.resize(trimmedLength);
|
||||||
SetConfig(keys, key.c_str(), value.c_str(), flags & CFG_DEBUG, filePath, line);
|
SetConfig(keys, key.c_str(), value.c_str(), flags & CFG_DEBUG, filePath, line);
|
||||||
newKeyValueInsertPos = configFileOutput.length();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
goto NewLine;
|
goto NewLine;
|
||||||
|
|
|
||||||
|
|
@ -3517,6 +3517,208 @@ localhostForwarding=true
|
||||||
configRead.close();
|
configRead.close();
|
||||||
VERIFY_ARE_EQUAL(customWslConfigContentActual, customWslConfigContentExpected);
|
VERIFY_ARE_EQUAL(customWslConfigContentActual, customWslConfigContentExpected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for GitHub issue #12671:
|
||||||
|
// Ensure that section headers always appear BEFORE their key-value pairs.
|
||||||
|
// Bug: WSL Settings GUI was writing keys before the section header, causing "Unknown key" errors.
|
||||||
|
{
|
||||||
|
std::wstring bugScenarioConfig =
|
||||||
|
LR"([wsl2]
|
||||||
|
[experimental]
|
||||||
|
[wsl2]
|
||||||
|
)";
|
||||||
|
WslConfigChange config{bugScenarioConfig.c_str()};
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
// Write memory setting - this should NOT appear before the first [wsl2]
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 17825792000ULL; // Value from bug report
|
||||||
|
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
// Read and verify
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Find FIRST occurrence of [wsl2] and memory=
|
||||||
|
auto firstWsl2Pos = fileContent.find(L"[wsl2]");
|
||||||
|
auto memoryPos = fileContent.find(L"memory=");
|
||||||
|
|
||||||
|
VERIFY_ARE_NOT_EQUAL(firstWsl2Pos, std::wstring::npos);
|
||||||
|
VERIFY_ARE_NOT_EQUAL(memoryPos, std::wstring::npos);
|
||||||
|
|
||||||
|
// The critical assertion: memory= must NOT appear before [wsl2]
|
||||||
|
VERIFY_IS_TRUE(firstWsl2Pos < memoryPos);
|
||||||
|
|
||||||
|
// Additional check: memory should appear after the first [wsl2], not after line 1
|
||||||
|
auto firstLineEnd = fileContent.find(L'\n');
|
||||||
|
VERIFY_IS_TRUE(memoryPos > firstLineEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Empty file - should create proper [wsl2] section structure
|
||||||
|
{
|
||||||
|
std::wofstream emptyConfig(apiWslConfigFilePath, std::ios::trunc);
|
||||||
|
emptyConfig.close();
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 4294967296ULL; // 4GB
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Should create [wsl2] section and add memory key
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"[wsl2]") != std::wstring::npos);
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"memory=") != std::wstring::npos);
|
||||||
|
// Verify [wsl2] comes before memory=
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"[wsl2]") < fileContent.find(L"memory="));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Multiple same-section instances - should update first occurrence
|
||||||
|
{
|
||||||
|
std::wofstream configFile(apiWslConfigFilePath, std::ios::trunc);
|
||||||
|
configFile << L"[wsl2]\n";
|
||||||
|
configFile << L"processors=4\n";
|
||||||
|
configFile << L"\n";
|
||||||
|
configFile << L"[experimental]\n";
|
||||||
|
configFile << L"autoProxy=true\n";
|
||||||
|
configFile << L"\n";
|
||||||
|
configFile << L"[wsl2]\n"; // Second [wsl2] section
|
||||||
|
configFile << L"swap=0\n";
|
||||||
|
configFile.close();
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 8589934592ULL; // 8GB
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Find first and second [wsl2]
|
||||||
|
auto firstWsl2 = fileContent.find(L"[wsl2]");
|
||||||
|
auto secondWsl2 = fileContent.find(L"[wsl2]", firstWsl2 + 1);
|
||||||
|
auto memoryPos = fileContent.find(L"memory=");
|
||||||
|
|
||||||
|
VERIFY_ARE_NOT_EQUAL(firstWsl2, std::wstring::npos);
|
||||||
|
VERIFY_ARE_NOT_EQUAL(secondWsl2, std::wstring::npos);
|
||||||
|
VERIFY_ARE_NOT_EQUAL(memoryPos, std::wstring::npos);
|
||||||
|
|
||||||
|
// Memory should be added to FIRST [wsl2] section, not second
|
||||||
|
VERIFY_IS_TRUE(memoryPos > firstWsl2);
|
||||||
|
VERIFY_IS_TRUE(memoryPos < secondWsl2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: EOF without trailing newline
|
||||||
|
{
|
||||||
|
std::wofstream configFile(apiWslConfigFilePath, std::ios::trunc);
|
||||||
|
configFile << L"[wsl2]\n";
|
||||||
|
configFile << L"processors=2"; // No trailing newline
|
||||||
|
configFile.close();
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 3221225472ULL; // 3GB
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Should properly append memory key even without trailing newline on last line
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"processors=2") != std::wstring::npos);
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"memory=") != std::wstring::npos);
|
||||||
|
|
||||||
|
// Verify both keys are in the same section
|
||||||
|
auto wsl2Pos = fileContent.find(L"[wsl2]");
|
||||||
|
auto processorsPos = fileContent.find(L"processors=2");
|
||||||
|
auto memoryPos = fileContent.find(L"memory=");
|
||||||
|
VERIFY_IS_TRUE(wsl2Pos < processorsPos);
|
||||||
|
VERIFY_IS_TRUE(wsl2Pos < memoryPos);
|
||||||
|
|
||||||
|
// Memory should come after processors in the same section
|
||||||
|
VERIFY_IS_TRUE(processorsPos < memoryPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Empty section followed by another section
|
||||||
|
{
|
||||||
|
std::wofstream configFile(apiWslConfigFilePath, std::ios::trunc);
|
||||||
|
configFile << L"[wsl2]\n";
|
||||||
|
configFile << L"[experimental]\n";
|
||||||
|
configFile << L"autoProxy=true\n";
|
||||||
|
configFile.close();
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 5368709120ULL; // 5GB
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Should insert memory into empty [wsl2] section before [experimental]
|
||||||
|
auto wsl2Pos = fileContent.find(L"[wsl2]");
|
||||||
|
auto memoryPos = fileContent.find(L"memory=");
|
||||||
|
auto experimentalPos = fileContent.find(L"[experimental]");
|
||||||
|
|
||||||
|
VERIFY_ARE_NOT_EQUAL(wsl2Pos, std::wstring::npos);
|
||||||
|
VERIFY_ARE_NOT_EQUAL(memoryPos, std::wstring::npos);
|
||||||
|
VERIFY_ARE_NOT_EQUAL(experimentalPos, std::wstring::npos);
|
||||||
|
|
||||||
|
// Order should be: [wsl2], memory=, [experimental]
|
||||||
|
VERIFY_IS_TRUE(wsl2Pos < memoryPos);
|
||||||
|
VERIFY_IS_TRUE(memoryPos < experimentalPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test: Section header at EOF with no content
|
||||||
|
{
|
||||||
|
std::wofstream configFile(apiWslConfigFilePath, std::ios::trunc);
|
||||||
|
configFile << L"[wsl2]"; // Section at EOF, no newline, no content
|
||||||
|
configFile.close();
|
||||||
|
|
||||||
|
wslConfig = createWslConfig(apiWslConfigFilePath);
|
||||||
|
VERIFY_IS_NOT_NULL(wslConfig);
|
||||||
|
auto cleanupWslConfig = wil::scope_exit([&] { freeWslConfig(wslConfig); });
|
||||||
|
|
||||||
|
WslConfigSetting memorySetting{};
|
||||||
|
memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
|
||||||
|
memorySetting.UInt64Value = 6442450944ULL; // 6GB
|
||||||
|
VERIFY_ARE_EQUAL(setWslConfigSetting(wslConfig, memorySetting), ERROR_SUCCESS);
|
||||||
|
|
||||||
|
std::wifstream configRead(apiWslConfigFilePath);
|
||||||
|
std::wstring fileContent{std::istreambuf_iterator<wchar_t>(configRead), {}};
|
||||||
|
configRead.close();
|
||||||
|
|
||||||
|
// Should properly add key to section at EOF
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"[wsl2]") != std::wstring::npos);
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"memory=") != std::wstring::npos);
|
||||||
|
VERIFY_IS_TRUE(fileContent.find(L"[wsl2]") < fileContent.find(L"memory="));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_METHOD(LaunchWslSettingsFromProtocol)
|
TEST_METHOD(LaunchWslSettingsFromProtocol)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue