1 | //===- llvm/unittest/Support/ReplaceFileTest.cpp - unit tests -------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "llvm/Support/Errc.h" |
10 | #include "llvm/Support/ErrorHandling.h" |
11 | #include "llvm/Support/FileSystem.h" |
12 | #include "llvm/Support/MemoryBuffer.h" |
13 | #include "llvm/Support/Path.h" |
14 | #include "llvm/Support/Process.h" |
15 | #include "gtest/gtest.h" |
16 | |
17 | using namespace llvm; |
18 | using namespace llvm::sys; |
19 | |
20 | #define ASSERT_NO_ERROR(x) \ |
21 | do { \ |
22 | if (std::error_code ASSERT_NO_ERROR_ec = x) { \ |
23 | errs() << #x ": did not return errc::success.\n" \ |
24 | << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" \ |
25 | << "error message: " << ASSERT_NO_ERROR_ec.message() << "\n"; \ |
26 | } \ |
27 | } while (false) |
28 | |
29 | namespace { |
30 | std::error_code CreateFileWithContent(const SmallString<128> &FilePath, |
31 | const StringRef &content) { |
32 | int FD = 0; |
33 | if (std::error_code ec = fs::openFileForWrite(Name: FilePath, ResultFD&: FD)) |
34 | return ec; |
35 | |
36 | const bool ShouldClose = true; |
37 | raw_fd_ostream OS(FD, ShouldClose); |
38 | OS << content; |
39 | |
40 | return std::error_code(); |
41 | } |
42 | |
43 | class ScopedFD { |
44 | int FD; |
45 | |
46 | ScopedFD(const ScopedFD &) = delete; |
47 | ScopedFD &operator=(const ScopedFD &) = delete; |
48 | |
49 | public: |
50 | explicit ScopedFD(int Descriptor) : FD(Descriptor) {} |
51 | ~ScopedFD() { Process::SafelyCloseFileDescriptor(FD); } |
52 | }; |
53 | |
54 | bool FDHasContent(int FD, StringRef Content) { |
55 | auto Buffer = |
56 | MemoryBuffer::getOpenFile(FD: sys::fs::convertFDToNativeFile(FD), Filename: "" , FileSize: -1); |
57 | assert(Buffer); |
58 | return Buffer.get()->getBuffer() == Content; |
59 | } |
60 | |
61 | bool FileHasContent(StringRef File, StringRef Content) { |
62 | int FD = 0; |
63 | auto EC = fs::openFileForRead(Name: File, ResultFD&: FD); |
64 | (void)EC; |
65 | assert(!EC); |
66 | ScopedFD EventuallyCloseIt(FD); |
67 | return FDHasContent(FD, Content); |
68 | } |
69 | |
70 | TEST(rename, FileOpenedForReadingCanBeReplaced) { |
71 | // Create unique temporary directory for this test. |
72 | SmallString<128> TestDirectory; |
73 | ASSERT_NO_ERROR(fs::createUniqueDirectory( |
74 | "FileOpenedForReadingCanBeReplaced-test" , TestDirectory)); |
75 | |
76 | // Add a couple of files to the test directory. |
77 | SmallString<128> SourceFileName(TestDirectory); |
78 | path::append(path&: SourceFileName, a: "source" ); |
79 | |
80 | SmallString<128> TargetFileName(TestDirectory); |
81 | path::append(path&: TargetFileName, a: "target" ); |
82 | |
83 | ASSERT_NO_ERROR(CreateFileWithContent(SourceFileName, "!!source!!" )); |
84 | ASSERT_NO_ERROR(CreateFileWithContent(TargetFileName, "!!target!!" )); |
85 | |
86 | { |
87 | // Open the target file for reading. |
88 | int ReadFD = 0; |
89 | ASSERT_NO_ERROR(fs::openFileForRead(TargetFileName, ReadFD)); |
90 | ScopedFD EventuallyCloseIt(ReadFD); |
91 | |
92 | // Confirm we can replace the file while it is open. |
93 | EXPECT_TRUE(!fs::rename(SourceFileName, TargetFileName)); |
94 | |
95 | // We should still be able to read the old data through the existing |
96 | // descriptor. |
97 | EXPECT_TRUE(FDHasContent(ReadFD, "!!target!!" )); |
98 | |
99 | // The source file should no longer exist |
100 | EXPECT_FALSE(fs::exists(SourceFileName)); |
101 | } |
102 | |
103 | // If we obtain a new descriptor for the target file, we should find that it |
104 | // contains the content that was in the source file. |
105 | EXPECT_TRUE(FileHasContent(TargetFileName, "!!source!!" )); |
106 | |
107 | // Rename the target file back to the source file name to confirm that rename |
108 | // still works if the destination does not already exist. |
109 | EXPECT_TRUE(!fs::rename(TargetFileName, SourceFileName)); |
110 | EXPECT_FALSE(fs::exists(TargetFileName)); |
111 | ASSERT_TRUE(fs::exists(SourceFileName)); |
112 | |
113 | // Clean up. |
114 | ASSERT_NO_ERROR(fs::remove(SourceFileName)); |
115 | ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); |
116 | } |
117 | |
118 | TEST(rename, ExistingTemp) { |
119 | // Test that existing .tmpN files don't get deleted by the Windows |
120 | // sys::fs::rename implementation. |
121 | SmallString<128> TestDirectory; |
122 | ASSERT_NO_ERROR( |
123 | fs::createUniqueDirectory("ExistingTemp-test" , TestDirectory)); |
124 | |
125 | SmallString<128> SourceFileName(TestDirectory); |
126 | path::append(path&: SourceFileName, a: "source" ); |
127 | |
128 | SmallString<128> TargetFileName(TestDirectory); |
129 | path::append(path&: TargetFileName, a: "target" ); |
130 | |
131 | SmallString<128> TargetTmp0FileName(TestDirectory); |
132 | path::append(path&: TargetTmp0FileName, a: "target.tmp0" ); |
133 | |
134 | SmallString<128> TargetTmp1FileName(TestDirectory); |
135 | path::append(path&: TargetTmp1FileName, a: "target.tmp1" ); |
136 | |
137 | ASSERT_NO_ERROR(CreateFileWithContent(SourceFileName, "!!source!!" )); |
138 | ASSERT_NO_ERROR(CreateFileWithContent(TargetFileName, "!!target!!" )); |
139 | ASSERT_NO_ERROR(CreateFileWithContent(TargetTmp0FileName, "!!target.tmp0!!" )); |
140 | |
141 | { |
142 | // Use mapped_file_region to make sure that the destination file is mmap'ed. |
143 | // This will cause SetInformationByHandle to fail when renaming to the |
144 | // destination, and we will follow the code path that tries to give target |
145 | // a temporary name. |
146 | int TargetFD; |
147 | std::error_code EC; |
148 | ASSERT_NO_ERROR(fs::openFileForRead(TargetFileName, TargetFD)); |
149 | ScopedFD X(TargetFD); |
150 | sys::fs::mapped_file_region MFR(sys::fs::convertFDToNativeFile(FD: TargetFD), |
151 | sys::fs::mapped_file_region::readonly, 10, |
152 | 0, EC); |
153 | ASSERT_FALSE(EC); |
154 | |
155 | ASSERT_NO_ERROR(fs::rename(SourceFileName, TargetFileName)); |
156 | |
157 | #ifdef _WIN32 |
158 | // Make sure that target was temporarily renamed to target.tmp1 on Windows. |
159 | // This is signified by a permission denied error as opposed to no such file |
160 | // or directory when trying to open it. |
161 | int Tmp1FD; |
162 | EXPECT_EQ(errc::permission_denied, |
163 | fs::openFileForRead(TargetTmp1FileName, Tmp1FD)); |
164 | #endif |
165 | } |
166 | |
167 | EXPECT_TRUE(FileHasContent(TargetTmp0FileName, "!!target.tmp0!!" )); |
168 | |
169 | ASSERT_NO_ERROR(fs::remove(TargetFileName)); |
170 | ASSERT_NO_ERROR(fs::remove(TargetTmp0FileName)); |
171 | ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); |
172 | } |
173 | |
174 | } // anonymous namespace |
175 | |