master
cpp 147 lines 5.65 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 FilesystemUnitTests.cpp
8
9 Abstract:
10
11 This file contains unit tests for the helpers in src/windows/common/filesystem.cpp.
12 These tests only read from the local filesystem so they do not require an installed distribution.
13
14 --*/
15
16 #include "precomp.h"
17 #include "Common.h"
18
19 using wsl::windows::common::filesystem::GetCanonicalPath;
20
21 namespace {
22
23 // Returns a file name that does not exist in the given directory.
24 std::wstring UniqueMissingName(const std::filesystem::path& Directory)
25 {
26 static int counter = 0;
27 const auto name = std::format(L"wsl_ut_canonical_{}_{}.txt", GetCurrentProcessId(), ++counter);
28 VERIFY_IS_FALSE(std::filesystem::exists(Directory / name));
29
30 return name;
31 }
32
33 // The canonical form of the current directory, which is what a relative path is expected to resolve
34 // against. std::filesystem::canonical is used rather than weakly_canonical so the expected value is
35 // computed independently of the API under test.
36 std::filesystem::path CanonicalCurrentDirectory()
37 {
38 return std::filesystem::canonical(std::filesystem::current_path());
39 }
40
41 // A path that std::filesystem::absolute is guaranteed to reject, because it exceeds the longest path
42 // Win32 can express. An empty path is not used: whether absolute rejects one is implementation
43 // defined, and some standard library versions accept it.
44 std::filesystem::path UnresolvablePath()
45 {
46 return {L"C:\\" + std::wstring(40000, L'a')};
47 }
48
49 // A file that is known to exist, used to cover paths that resolve to a real filesystem entry. The
50 // test module itself is used so that no file has to be created.
51 std::filesystem::path ExistingFile()
52 {
53 return {wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle())};
54 }
55
56 } // namespace
57
58 namespace FilesystemUnitTests {
59 class FilesystemUnitTests
60 {
61 WSL_TEST_CLASS(FilesystemUnitTests)
62
63 // A relative path naming a file that does not exist must still resolve to an absolute path.
64 // std::filesystem::weakly_canonical cannot do this on its own: it builds its result from the
65 // longest leading sequence of elements that exist, so a bare missing file name has nothing to
66 // canonicalize and is returned unchanged.
67 TEST_METHOD(GetCanonicalPath_RelativeMissingPathIsMadeAbsolute)
68 {
69 const auto name = UniqueMissingName(std::filesystem::current_path());
70 VERIFY_IS_FALSE(std::filesystem::weakly_canonical(name).is_absolute());
71
72 const auto result = GetCanonicalPath(name);
73
74 VERIFY_IS_TRUE(result.is_absolute());
75 VERIFY_ARE_EQUAL((CanonicalCurrentDirectory() / name).wstring(), result.wstring());
76 }
77
78 // The same resolution must happen for a relative path whose target already exists.
79 TEST_METHOD(GetCanonicalPath_RelativeExistingPathIsMadeAbsolute)
80 {
81 const auto existing = ExistingFile();
82 const auto relativePath = std::filesystem::relative(existing, std::filesystem::current_path());
83 VERIFY_IS_FALSE(relativePath.empty());
84 VERIFY_IS_FALSE(relativePath.is_absolute());
85
86 const auto result = GetCanonicalPath(relativePath);
87
88 VERIFY_IS_TRUE(result.is_absolute());
89 VERIFY_ARE_EQUAL(std::filesystem::canonical(existing).wstring(), result.wstring());
90 }
91
92 // '.' and '..' components must be collapsed even when the intermediate directory does not exist.
93 TEST_METHOD(GetCanonicalPath_CollapsesDotSegments)
94 {
95 const auto name = UniqueMissingName(std::filesystem::current_path());
96
97 const auto result = GetCanonicalPath(L".\\nonexistent\\..\\" + name);
98
99 VERIFY_ARE_EQUAL((CanonicalCurrentDirectory() / name).wstring(), result.wstring());
100 }
101
102 // An already absolute path must be returned unchanged.
103 TEST_METHOD(GetCanonicalPath_AbsolutePathIsUnchanged)
104 {
105 const auto expected = CanonicalCurrentDirectory() / UniqueMissingName(std::filesystem::current_path());
106
107 VERIFY_ARE_EQUAL(expected.wstring(), GetCanonicalPath(expected).wstring());
108 }
109
110 // A failure from std::filesystem::absolute must be reported. absolute returns an empty path when
111 // it fails, and weakly_canonical succeeds on an empty path and clears the error_code, so calling
112 // the two in sequence without checking in between silently turns the failure into success.
113 TEST_METHOD(GetCanonicalPath_ErrorOverloadReportsFailure)
114 {
115 std::error_code error;
116 const auto result = GetCanonicalPath(UnresolvablePath(), error);
117
118 VERIFY_ARE_NOT_EQUAL(std::error_code{}, error);
119 VERIFY_IS_TRUE(result.empty());
120 }
121
122 // Error must be cleared when the call succeeds so callers can reuse the same variable.
123 TEST_METHOD(GetCanonicalPath_ErrorOverloadClearsErrorOnSuccess)
124 {
125 const auto expected = CanonicalCurrentDirectory() / UniqueMissingName(std::filesystem::current_path());
126
127 auto error = std::make_error_code(std::errc::permission_denied);
128 const auto result = GetCanonicalPath(expected, error);
129
130 VERIFY_ARE_EQUAL(std::error_code{}, error);
131 VERIFY_ARE_EQUAL(expected.wstring(), result.wstring());
132 }
133
134 // The throwing overload must surface the same failure the non-throwing overload reports.
135 TEST_METHOD(GetCanonicalPath_ThrowingOverloadSurfacesFailure)
136 {
137 std::error_code error;
138 (void)GetCanonicalPath(UnresolvablePath(), error);
139 VERIFY_ARE_NOT_EQUAL(std::error_code{}, error);
140
141 const auto expectedResult = HRESULT_FROM_WIN32(error.value());
142 VERIFY_THROWS_SPECIFIC(GetCanonicalPath(UnresolvablePath()), wil::ResultException, [&](const wil::ResultException& e) {
143 return e.GetErrorCode() == expectedResult;
144 });
145 }
146 };
147 } // namespace FilesystemUnitTests