master
cpp 338 lines 14.5 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 SimpleTests.cpp
8
9 Abstract:
10
11 This file contains smoke tests for WSL.
12
13 --*/
14
15 #include "precomp.h"
16 #include "Common.h"
17
18 namespace SimpleTests {
19 class SimpleTests
20 {
21 WSL_TEST_CLASS(SimpleTests)
22
23 // Initialize the tests
24 TEST_CLASS_SETUP(TestClassSetup)
25 {
26 VERIFY_ARE_EQUAL(LxsstuInitialize(FALSE), TRUE);
27
28 return true;
29 }
30
31 TEST_CLASS_CLEANUP(TestClassCleanup)
32 {
33 LxsstuUninitialize(FALSE);
34 return true;
35 }
36
37 TEST_METHOD(EchoTest)
38 {
39 const std::wstring echoExpected = L"LOW!\n";
40 auto [output, __] = LxsstuLaunchWslAndCaptureOutput(L"echo LOW!");
41 VERIFY_ARE_EQUAL(output, echoExpected);
42 }
43
44 TEST_METHOD(WhoamiTest)
45 {
46 const std::wstring whoamiExpected = L"root\n";
47 auto [output, __] = LxsstuLaunchWslAndCaptureOutput(L"-u root whoami");
48 VERIFY_ARE_EQUAL(output, whoamiExpected);
49 }
50
51 TEST_METHOD(ChangeDirTest)
52 {
53 const std::wstring cdExpected = L"/root\n";
54 auto [output, __] = LxsstuLaunchWslAndCaptureOutput(L"--cd ~ --user root pwd");
55 VERIFY_ARE_EQUAL(output, cdExpected);
56 }
57
58 TEST_METHOD(Daemonize)
59 {
60 WslConfigChange config(LxssGenerateTestConfig({.vmIdleTimeout = 0}));
61 WslShutdown();
62 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"-- eval \"touch /dev/shm/backgroundmagic; daemonize $(which sleep) 30\""), (DWORD)0);
63
64 std::this_thread::sleep_for(std::chrono::seconds(20));
65
66 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(L"-- ls /dev/shm/backgroundmagic"), (DWORD)0);
67 }
68
69 static void VerifySparse(wchar_t const* path, bool sparse)
70 {
71 DWORD attributes = ::GetFileAttributesW(path);
72 VERIFY_IS_FALSE(attributes == INVALID_FILE_ATTRIBUTES);
73 VERIFY_IS_TRUE(WI_IsFlagSet(attributes, FILE_ATTRIBUTE_SPARSE_FILE) == sparse);
74 }
75
76 WSL2_TEST_METHOD(CheckSparse)
77 {
78 WslConfigChange config(LxssGenerateTestConfig({.sparse = true}));
79
80 std::filesystem::path tar = std::tmpnam(nullptr);
81 tar += ".tar";
82 LogInfo("tar %ls", tar.c_str());
83 auto cleanupTar = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
84 try
85 {
86 std::filesystem::remove(tar);
87 }
88 CATCH_LOG()
89 });
90
91 const std::wstring tempDistro = L"temp_distro";
92 const std::filesystem::path vhdDir = std::tmpnam(nullptr);
93 LogInfo("vhdDir %ls", vhdDir.c_str());
94 VERIFY_IS_TRUE(std::filesystem::create_directory(vhdDir));
95 auto cleanup = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
96 try
97 {
98 LxsstuLaunchWsl(std::format(L"{} {}", WSL_UNREGISTER_ARG, tempDistro).c_str());
99 std::filesystem::remove_all(vhdDir);
100 }
101 CATCH_LOG()
102 });
103
104 VERIFY_ARE_EQUAL(LxsstuLaunchWsl(std::format(L"{} {} {}", WSL_EXPORT_ARG, LXSS_DISTRO_NAME_TEST, tar.wstring()).c_str()), (DWORD)0);
105 LxsstuLaunchWsl(std::format(L"{} {}", WSL_UNREGISTER_ARG, tempDistro).c_str());
106 ValidateOutput(
107 std::format(L"{} {} {} {}", WSL_IMPORT_ARG, tempDistro, vhdDir.wstring(), tar.wstring()).c_str(),
108 L"The operation completed successfully. \r\n",
109 L"wsl: Sparse VHD support is currently disabled due to potential data corruption.\r\n"
110 L"To force a distribution to use a sparse VHD, please run:\r\n"
111 L"wsl.exe --manage <DistributionName> --set-sparse true --allow-unsafe\r\n",
112 0);
113
114 std::filesystem::path vhdPath = vhdDir / LXSS_VM_MODE_VHD_NAME;
115 VerifySparse(vhdPath.c_str(), false);
116
117 WslShutdown();
118
119 // Setting a distro VHD to sparse requires the allow unsafe flag.
120 ValidateOutput(
121 std::format(L"{} {} {} {}", WSL_MANAGE_ARG, tempDistro, WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, L"true").c_str(),
122 FormatErrorMessage(
123 L"Sparse VHD support is currently disabled due to potential data corruption.\r\n"
124 L"To force a distribution to use a sparse VHD, please run:\r\n"
125 L"wsl.exe --manage <DistributionName> --set-sparse true --allow-unsafe",
126 L"Wsl/Service/E_INVALIDARG"),
127 L"",
128 -1);
129
130 VerifySparse(vhdPath.c_str(), false);
131
132 ValidateOutput(
133 std::format(L"{} {} {} {} {}", WSL_MANAGE_ARG, tempDistro, WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, L"true", WSL_MANAGE_ARG_ALLOW_UNSAFE)
134 .c_str(),
135 L"The operation completed successfully. \r\n",
136 L"",
137 0);
138
139 VerifySparse(vhdPath.c_str(), true);
140
141 // Disabling sparse on a VHD does not require the allow unsafe flag.
142 ValidateOutput(
143 std::format(L"{} {} {} {}", WSL_MANAGE_ARG, tempDistro, WSL_MANAGE_ARG_SET_SPARSE_OPTION_LONG, L"false").c_str(),
144 L"The operation completed successfully. \r\n",
145 L"",
146 0);
147
148 VerifySparse(vhdPath.c_str(), false);
149 }
150
151 TEST_METHOD(StringHelpers)
152 {
153 std::string string1 = "aaaBBB";
154 std::string string2 = "aaabbb";
155 VERIFY_IS_TRUE(wsl::shared::string::IsEqual(string1, string2, true));
156 VERIFY_IS_FALSE(wsl::shared::string::IsEqual(string1, string2, false));
157 VERIFY_IS_TRUE(wsl::shared::string::IsEqual(string1.c_str(), string2.c_str(), true));
158 VERIFY_IS_FALSE(wsl::shared::string::IsEqual(string1.c_str(), string2.c_str(), false));
159 VERIFY_IS_TRUE(wsl::shared::string::StartsWith(string1, string2.substr(0, 3), true));
160 VERIFY_IS_FALSE(wsl::shared::string::StartsWith(string1, string2, false));
161
162 std::wstring wstring1 = L"aaaBBB";
163 std::wstring wstring2 = L"aaabbb";
164 VERIFY_IS_TRUE(wsl::shared::string::IsEqual(wstring1, wstring2, true));
165 VERIFY_IS_FALSE(wsl::shared::string::IsEqual(wstring1, wstring2, false));
166 VERIFY_IS_TRUE(wsl::shared::string::IsEqual(wstring1.c_str(), wstring2.c_str(), true));
167 VERIFY_IS_FALSE(wsl::shared::string::IsEqual(wstring1.c_str(), wstring2.c_str(), false));
168 VERIFY_IS_TRUE(wsl::shared::string::StartsWith(wstring1, wstring2.substr(0, 3), true));
169 VERIFY_IS_FALSE(wsl::shared::string::StartsWith(wstring1, wstring2, false));
170
171 // Test wsl::shared::string::ParseBool
172 std::vector<std::pair<LPCSTR, std::optional<bool>>> boolTests = {
173 {"1", true},
174 {"0", false},
175 {"true", true},
176 {"false", false},
177 {"True", true},
178 {"False", false},
179 {"t", std::nullopt},
180 {"f", std::nullopt},
181 {"T", std::nullopt},
182 {"F", std::nullopt},
183 {nullptr, std::nullopt},
184 {"", std::nullopt},
185 {"2", std::nullopt},
186 {"true_", std::nullopt},
187 {"false_", std::nullopt},
188 };
189
190 for (const auto& [input, expected] : boolTests)
191 {
192 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ParseBool(input));
193
194 std::wstring wideString = wsl::shared::string::MultiByteToWide(input);
195 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ParseBool(wideString.c_str()));
196 }
197
198 // With AllowExtendedForms the single-letter "t"/"f" forms (case-insensitive) are also
199 // recognized, matching Go's strconv.ParseBool (and therefore the Docker CLI). Every
200 // form accepted by default must still parse identically in extended mode.
201 std::vector<std::pair<LPCSTR, std::optional<bool>>> extendedBoolTests = {
202 {"1", true},
203 {"0", false},
204 {"true", true},
205 {"false", false},
206 {"True", true},
207 {"False", false},
208 {"t", true},
209 {"T", true},
210 {"f", false},
211 {"F", false},
212 {nullptr, std::nullopt},
213 {"", std::nullopt},
214 {"2", std::nullopt},
215 {"tr", std::nullopt},
216 {"true_", std::nullopt},
217 {"false_", std::nullopt},
218 };
219
220 for (const auto& [input, expected] : extendedBoolTests)
221 {
222 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ParseBool(input, true));
223
224 std::wstring wideString = wsl::shared::string::MultiByteToWide(input);
225 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ParseBool(wideString.c_str(), true));
226 }
227
228 // Test wsl::shared::string GUID helpers
229 const GUID guid = {0x1234567a, 0x1234, 0x5678, {0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78}};
230 const std::string guidString = "{1234567a-1234-5678-1234-567812345678}";
231 const std::string guidStringNoBraces = "1234567a-1234-5678-1234-567812345678";
232 const std::vector<std::pair<LPCSTR, std::optional<GUID>>> guidTestCases{
233 {guidString.c_str(), guid},
234 {guidStringNoBraces.c_str(), guid},
235 {nullptr, std::nullopt},
236 {"", std::nullopt},
237 {"foo", std::nullopt},
238 {"1234567G-1234-5678-1234-5678123456789", std::nullopt},
239 {"{1234567a-1234-5678-1234-567812345678", std::nullopt},
240 {"{1234567aB-1234-5678-1234-567812345678}", std::nullopt}};
241
242 for (const auto& [input, expected] : guidTestCases)
243 {
244 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ToGuid(input));
245 const auto wideInput = wsl::shared::string::MultiByteToWide(input);
246 VERIFY_ARE_EQUAL(expected, wsl::shared::string::ToGuid(wideInput));
247 }
248
249 VERIFY_ARE_EQUAL(guidString, wsl::shared::string::GuidToString<char>(guid));
250 VERIFY_ARE_EQUAL(guidString, wsl::shared::string::GuidToString<char>(guid, wsl::shared::string::GuidToStringFlags::AddBraces));
251 VERIFY_ARE_EQUAL(guidStringNoBraces, wsl::shared::string::GuidToString<char>(guid, wsl::shared::string::GuidToStringFlags::None));
252
253 auto upperCaseGuidString = guidStringNoBraces;
254 std::transform(upperCaseGuidString.begin(), upperCaseGuidString.end(), upperCaseGuidString.begin(), toupper);
255 VERIFY_ARE_EQUAL(upperCaseGuidString, wsl::shared::string::GuidToString<char>(guid, wsl::shared::string::GuidToStringFlags::Uppercase));
256
257 const auto wideGuidString = wsl::shared::string::MultiByteToWide(guidString);
258 VERIFY_ARE_EQUAL(wideGuidString, wsl::shared::string::GuidToString<wchar_t>(guid));
259
260 VERIFY_ARE_EQUAL(wideGuidString, wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::AddBraces));
261 const auto wideGuidStringNoBraces = wsl::shared::string::MultiByteToWide(guidStringNoBraces);
262 VERIFY_ARE_EQUAL(wideGuidStringNoBraces, wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::None));
263
264 auto upperCaseGuidStringWide = wideGuidStringNoBraces;
265 std::transform(upperCaseGuidStringWide.begin(), upperCaseGuidStringWide.end(), upperCaseGuidStringWide.begin(), toupper);
266 VERIFY_ARE_EQUAL(upperCaseGuidStringWide, wsl::shared::string::GuidToString<wchar_t>(guid, wsl::shared::string::GuidToStringFlags::Uppercase));
267
268 VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::string{" \tvalue\r\n"}), std::string{"value"});
269 VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::string{" \t\r\n"}), std::string{});
270 VERIFY_ARE_EQUAL(wsl::shared::string::Trim(std::wstring{L" \tvalue\r\n"}), std::wstring{L"value"});
271
272 const std::vector<std::pair<std::string, std::string>> shellStrings{
273 {"", ""},
274 {"plain", "plain"},
275 {"\"\"", ""},
276 {"''", ""},
277 {"\"double quoted\"", "double quoted"},
278 {"'single quoted'", "single quoted"},
279 {"one' two'\" three\"", "one two three"},
280 {"escaped\\ value", "escaped value"},
281 {"escaped\\q", "escapedq"},
282 {"escaped\\'quote", "escaped'quote"},
283 {"abc\\\nedf", "abcedf"},
284 {"\"abc\\\nedf\"", "abcedf"},
285 {"'abc\\\nedf'", "abc\\\nedf"},
286 {"\"escaped \\\"quote\\\" and \\\\ slash\"", "escaped \"quote\" and \\ slash"},
287 {"\"escaped \\$dollar and \\`backtick\"", "escaped $dollar and `backtick"},
288 {"\"literal \\q\"", "literal \\q"},
289 {"'literal \\ value'", "literal \\ value"},
290 {"unterminated'", "unterminated'"},
291 {"\"unterminated", "\"unterminated"},
292 {"trailing\\", "trailing\\"},
293 {"\"trailing\\", "\"trailing\\"}};
294
295 for (const auto& [input, expected] : shellStrings)
296 {
297 VERIFY_ARE_EQUAL(wsl::shared::string::UnescapeShell(input), expected);
298 VERIFY_ARE_EQUAL(
299 wsl::shared::string::UnescapeShell(wsl::shared::string::MultiByteToWide(input)),
300 wsl::shared::string::MultiByteToWide(expected));
301 }
302 }
303
304 TEST_METHOD(WindowsPathWithSpaces)
305 {
306 wil::unique_environstrings_ptr originalPath;
307 const DWORD pathLength = GetEnvironmentVariableW(L"PATH", nullptr, 0);
308 if (pathLength > 0)
309 {
310 originalPath.reset(static_cast<PWSTR>(HeapAlloc(GetProcessHeap(), 0, pathLength * sizeof(wchar_t))));
311 THROW_LAST_ERROR_IF_NULL(originalPath.get());
312 THROW_LAST_ERROR_IF(GetEnvironmentVariableW(L"PATH", originalPath.get(), pathLength) == 0);
313 }
314
315 auto cleanup = wil::scope_exit([&]() {
316 if (originalPath)
317 {
318 THROW_LAST_ERROR_IF(!SetEnvironmentVariableW(L"PATH", originalPath.get()));
319 }
320 });
321
322 const wchar_t* testPath =
323 L"C:\\Program Files\\Git\\cmd;"
324 L"C:\\Program Files\\PowerShell\\7;"
325 L"C:\\Program Files (x86)\\Common Files;"
326 L"C:\\Users\\Test User\\AppData\\Local\\Programs\\Microsoft VS Code\\bin";
327
328 THROW_LAST_ERROR_IF(!SetEnvironmentVariableW(L"PATH", testPath));
329
330 auto [output, _] = LxsstuLaunchWslAndCaptureOutput(L"echo $PATH");
331
332 VERIFY_IS_TRUE(output.find(L"/mnt/c/Program Files/Git/cmd") != std::wstring::npos);
333 VERIFY_IS_TRUE(output.find(L"/mnt/c/Program Files/PowerShell/7") != std::wstring::npos);
334 VERIFY_IS_TRUE(output.find(L"/mnt/c/Program Files (x86)/Common Files") != std::wstring::npos);
335 VERIFY_IS_TRUE(output.find(L"/mnt/c/Users/Test User/AppData/Local/Programs/Microsoft VS Code/bin") != std::wstring::npos);
336 }
337 };
338 } // namespace SimpleTests