@@ -3517,6 +3517,208 @@ localhostForwarding=true
35173517 configRead.close ();
35183518 VERIFY_ARE_EQUAL (customWslConfigContentActual, customWslConfigContentExpected);
35193519 }
3520+
3521+ // Regression test for GitHub issue #12671:
3522+ // Ensure that section headers always appear BEFORE their key-value pairs.
3523+ // Bug: WSL Settings GUI was writing keys before the section header, causing "Unknown key" errors.
3524+ {
3525+ std::wstring bugScenarioConfig =
3526+ LR"( [wsl2]
3527+ [experimental]
3528+ [wsl2]
3529+ )" ;
3530+ WslConfigChange config{bugScenarioConfig.c_str ()};
3531+
3532+ wslConfig = createWslConfig (apiWslConfigFilePath);
3533+ VERIFY_IS_NOT_NULL (wslConfig);
3534+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3535+
3536+ // Write memory setting - this should NOT appear before the first [wsl2]
3537+ WslConfigSetting memorySetting{};
3538+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3539+ memorySetting.UInt64Value = 17825792000ULL ; // Value from bug report
3540+
3541+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3542+
3543+ // Read and verify
3544+ std::wifstream configRead (apiWslConfigFilePath);
3545+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3546+ configRead.close ();
3547+
3548+ // Find FIRST occurrence of [wsl2] and memory=
3549+ auto firstWsl2Pos = fileContent.find (L" [wsl2]" );
3550+ auto memoryPos = fileContent.find (L" memory=" );
3551+
3552+ VERIFY_ARE_NOT_EQUAL (firstWsl2Pos, std::wstring::npos);
3553+ VERIFY_ARE_NOT_EQUAL (memoryPos, std::wstring::npos);
3554+
3555+ // The critical assertion: memory= must NOT appear before [wsl2]
3556+ VERIFY_IS_TRUE (firstWsl2Pos < memoryPos);
3557+
3558+ // Additional check: memory should appear after the first [wsl2], not after line 1
3559+ auto firstLineEnd = fileContent.find (L' \n ' );
3560+ VERIFY_IS_TRUE (memoryPos > firstLineEnd);
3561+ }
3562+
3563+ // Test: Empty file - should create proper [wsl2] section structure
3564+ {
3565+ std::wofstream emptyConfig (apiWslConfigFilePath, std::ios::trunc);
3566+ emptyConfig.close ();
3567+
3568+ wslConfig = createWslConfig (apiWslConfigFilePath);
3569+ VERIFY_IS_NOT_NULL (wslConfig);
3570+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3571+
3572+ WslConfigSetting memorySetting{};
3573+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3574+ memorySetting.UInt64Value = 4294967296ULL ; // 4GB
3575+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3576+
3577+ std::wifstream configRead (apiWslConfigFilePath);
3578+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3579+ configRead.close ();
3580+
3581+ // Should create [wsl2] section and add memory key
3582+ VERIFY_IS_TRUE (fileContent.find (L" [wsl2]" ) != std::wstring::npos);
3583+ VERIFY_IS_TRUE (fileContent.find (L" memory=" ) != std::wstring::npos);
3584+ // Verify [wsl2] comes before memory=
3585+ VERIFY_IS_TRUE (fileContent.find (L" [wsl2]" ) < fileContent.find (L" memory=" ));
3586+ }
3587+
3588+ // Test: Multiple same-section instances - should update first occurrence
3589+ {
3590+ std::wofstream configFile (apiWslConfigFilePath, std::ios::trunc);
3591+ configFile << L" [wsl2]\n " ;
3592+ configFile << L" processors=4\n " ;
3593+ configFile << L" \n " ;
3594+ configFile << L" [experimental]\n " ;
3595+ configFile << L" autoProxy=true\n " ;
3596+ configFile << L" \n " ;
3597+ configFile << L" [wsl2]\n " ; // Second [wsl2] section
3598+ configFile << L" swap=0\n " ;
3599+ configFile.close ();
3600+
3601+ wslConfig = createWslConfig (apiWslConfigFilePath);
3602+ VERIFY_IS_NOT_NULL (wslConfig);
3603+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3604+
3605+ WslConfigSetting memorySetting{};
3606+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3607+ memorySetting.UInt64Value = 8589934592ULL ; // 8GB
3608+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3609+
3610+ std::wifstream configRead (apiWslConfigFilePath);
3611+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3612+ configRead.close ();
3613+
3614+ // Find first and second [wsl2]
3615+ auto firstWsl2 = fileContent.find (L" [wsl2]" );
3616+ auto secondWsl2 = fileContent.find (L" [wsl2]" , firstWsl2 + 1 );
3617+ auto memoryPos = fileContent.find (L" memory=" );
3618+
3619+ VERIFY_ARE_NOT_EQUAL (firstWsl2, std::wstring::npos);
3620+ VERIFY_ARE_NOT_EQUAL (secondWsl2, std::wstring::npos);
3621+ VERIFY_ARE_NOT_EQUAL (memoryPos, std::wstring::npos);
3622+
3623+ // Memory should be added to FIRST [wsl2] section, not second
3624+ VERIFY_IS_TRUE (memoryPos > firstWsl2);
3625+ VERIFY_IS_TRUE (memoryPos < secondWsl2);
3626+ }
3627+
3628+ // Test: EOF without trailing newline
3629+ {
3630+ std::wofstream configFile (apiWslConfigFilePath, std::ios::trunc);
3631+ configFile << L" [wsl2]\n " ;
3632+ configFile << L" processors=2" ; // No trailing newline
3633+ configFile.close ();
3634+
3635+ wslConfig = createWslConfig (apiWslConfigFilePath);
3636+ VERIFY_IS_NOT_NULL (wslConfig);
3637+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3638+
3639+ WslConfigSetting memorySetting{};
3640+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3641+ memorySetting.UInt64Value = 3221225472ULL ; // 3GB
3642+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3643+
3644+ std::wifstream configRead (apiWslConfigFilePath);
3645+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3646+ configRead.close ();
3647+
3648+ // Should properly append memory key even without trailing newline on last line
3649+ VERIFY_IS_TRUE (fileContent.find (L" processors=2" ) != std::wstring::npos);
3650+ VERIFY_IS_TRUE (fileContent.find (L" memory=" ) != std::wstring::npos);
3651+
3652+ // Verify both keys are in the same section
3653+ auto wsl2Pos = fileContent.find (L" [wsl2]" );
3654+ auto processorsPos = fileContent.find (L" processors=2" );
3655+ auto memoryPos = fileContent.find (L" memory=" );
3656+ VERIFY_IS_TRUE (wsl2Pos < processorsPos);
3657+ VERIFY_IS_TRUE (wsl2Pos < memoryPos);
3658+
3659+ // Memory should come after processors in the same section
3660+ VERIFY_IS_TRUE (processorsPos < memoryPos);
3661+ }
3662+
3663+ // Test: Empty section followed by another section
3664+ {
3665+ std::wofstream configFile (apiWslConfigFilePath, std::ios::trunc);
3666+ configFile << L" [wsl2]\n " ;
3667+ configFile << L" [experimental]\n " ;
3668+ configFile << L" autoProxy=true\n " ;
3669+ configFile.close ();
3670+
3671+ wslConfig = createWslConfig (apiWslConfigFilePath);
3672+ VERIFY_IS_NOT_NULL (wslConfig);
3673+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3674+
3675+ WslConfigSetting memorySetting{};
3676+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3677+ memorySetting.UInt64Value = 5368709120ULL ; // 5GB
3678+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3679+
3680+ std::wifstream configRead (apiWslConfigFilePath);
3681+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3682+ configRead.close ();
3683+
3684+ // Should insert memory into empty [wsl2] section before [experimental]
3685+ auto wsl2Pos = fileContent.find (L" [wsl2]" );
3686+ auto memoryPos = fileContent.find (L" memory=" );
3687+ auto experimentalPos = fileContent.find (L" [experimental]" );
3688+
3689+ VERIFY_ARE_NOT_EQUAL (wsl2Pos, std::wstring::npos);
3690+ VERIFY_ARE_NOT_EQUAL (memoryPos, std::wstring::npos);
3691+ VERIFY_ARE_NOT_EQUAL (experimentalPos, std::wstring::npos);
3692+
3693+ // Order should be: [wsl2], memory=, [experimental]
3694+ VERIFY_IS_TRUE (wsl2Pos < memoryPos);
3695+ VERIFY_IS_TRUE (memoryPos < experimentalPos);
3696+ }
3697+
3698+ // Test: Section header at EOF with no content
3699+ {
3700+ std::wofstream configFile (apiWslConfigFilePath, std::ios::trunc);
3701+ configFile << L" [wsl2]" ; // Section at EOF, no newline, no content
3702+ configFile.close ();
3703+
3704+ wslConfig = createWslConfig (apiWslConfigFilePath);
3705+ VERIFY_IS_NOT_NULL (wslConfig);
3706+ auto cleanupWslConfig = wil::scope_exit ([&] { freeWslConfig (wslConfig); });
3707+
3708+ WslConfigSetting memorySetting{};
3709+ memorySetting.ConfigEntry = WslConfigEntry::MemorySizeBytes;
3710+ memorySetting.UInt64Value = 6442450944ULL ; // 6GB
3711+ VERIFY_ARE_EQUAL (setWslConfigSetting (wslConfig, memorySetting), ERROR_SUCCESS);
3712+
3713+ std::wifstream configRead (apiWslConfigFilePath);
3714+ std::wstring fileContent{std::istreambuf_iterator<wchar_t >(configRead), {}};
3715+ configRead.close ();
3716+
3717+ // Should properly add key to section at EOF
3718+ VERIFY_IS_TRUE (fileContent.find (L" [wsl2]" ) != std::wstring::npos);
3719+ VERIFY_IS_TRUE (fileContent.find (L" memory=" ) != std::wstring::npos);
3720+ VERIFY_IS_TRUE (fileContent.find (L" [wsl2]" ) < fileContent.find (L" memory=" ));
3721+ }
35203722 }
35213723
35223724 TEST_METHOD (LaunchWslSettingsFromProtocol)
0 commit comments