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