]>
Commit | Line | Data |
---|---|---|
1d09f67e TL |
1 | // Licensed to the Apache Software Foundation (ASF) under one |
2 | // std::unique_ptr<TemporaryDir> temp_dir; | |
3 | // or more contributor license agreements. See the NOTICE file | |
4 | // distributed with this work for additional information | |
5 | // regarding copyright ownership. The ASF licenses this file | |
6 | // to you under the Apache License, Version 2.0 (the | |
7 | // "License"); you may not use this file except in compliance | |
8 | // with the License. You may obtain a copy of the License at | |
9 | // | |
10 | // http://www.apache.org/licenses/LICENSE-2.0 | |
11 | // | |
12 | // Unless required by applicable law or agreed to in writing, | |
13 | // software distributed under the License is distributed on an | |
14 | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
15 | // KIND, either express or implied. See the License for the | |
16 | // specific language governing permissions and limitations | |
17 | // under the License. | |
18 | ||
19 | #include <algorithm> | |
20 | #include <atomic> | |
21 | #include <cerrno> | |
22 | #include <limits> | |
23 | #include <sstream> | |
24 | #include <vector> | |
25 | ||
26 | #include <signal.h> | |
27 | ||
28 | #ifndef _WIN32 | |
29 | #include <pthread.h> | |
30 | #endif | |
31 | ||
32 | #include <gmock/gmock-matchers.h> | |
33 | #include <gtest/gtest.h> | |
34 | ||
35 | #include "arrow/testing/gtest_util.h" | |
36 | #include "arrow/util/bit_util.h" | |
37 | #include "arrow/util/io_util.h" | |
38 | #include "arrow/util/logging.h" | |
39 | #include "arrow/util/windows_compatibility.h" | |
40 | #include "arrow/util/windows_fixup.h" | |
41 | ||
42 | namespace arrow { | |
43 | namespace internal { | |
44 | ||
45 | void AssertExists(const PlatformFilename& path) { | |
46 | bool exists = false; | |
47 | ASSERT_OK_AND_ASSIGN(exists, FileExists(path)); | |
48 | ASSERT_TRUE(exists) << "Path '" << path.ToString() << "' doesn't exist"; | |
49 | } | |
50 | ||
51 | void AssertNotExists(const PlatformFilename& path) { | |
52 | bool exists = true; | |
53 | ASSERT_OK_AND_ASSIGN(exists, FileExists(path)); | |
54 | ASSERT_FALSE(exists) << "Path '" << path.ToString() << "' exists"; | |
55 | } | |
56 | ||
57 | void TouchFile(const PlatformFilename& path) { | |
58 | int fd = -1; | |
59 | ASSERT_OK_AND_ASSIGN(fd, FileOpenWritable(path)); | |
60 | ASSERT_OK(FileClose(fd)); | |
61 | } | |
62 | ||
63 | TEST(ErrnoFromStatus, Basics) { | |
64 | Status st; | |
65 | st = Status::OK(); | |
66 | ASSERT_EQ(ErrnoFromStatus(st), 0); | |
67 | st = Status::KeyError("foo"); | |
68 | ASSERT_EQ(ErrnoFromStatus(st), 0); | |
69 | st = Status::IOError("foo"); | |
70 | ASSERT_EQ(ErrnoFromStatus(st), 0); | |
71 | st = StatusFromErrno(EINVAL, StatusCode::KeyError, "foo"); | |
72 | ASSERT_EQ(ErrnoFromStatus(st), EINVAL); | |
73 | st = IOErrorFromErrno(EPERM, "foo"); | |
74 | ASSERT_EQ(ErrnoFromStatus(st), EPERM); | |
75 | st = IOErrorFromErrno(6789, "foo"); | |
76 | ASSERT_EQ(ErrnoFromStatus(st), 6789); | |
77 | ||
78 | st = CancelledFromSignal(SIGINT, "foo"); | |
79 | ASSERT_EQ(ErrnoFromStatus(st), 0); | |
80 | } | |
81 | ||
82 | TEST(SignalFromStatus, Basics) { | |
83 | Status st; | |
84 | st = Status::OK(); | |
85 | ASSERT_EQ(SignalFromStatus(st), 0); | |
86 | st = Status::KeyError("foo"); | |
87 | ASSERT_EQ(SignalFromStatus(st), 0); | |
88 | st = Status::Cancelled("foo"); | |
89 | ASSERT_EQ(SignalFromStatus(st), 0); | |
90 | st = StatusFromSignal(SIGINT, StatusCode::KeyError, "foo"); | |
91 | ASSERT_EQ(SignalFromStatus(st), SIGINT); | |
92 | ASSERT_EQ(st.ToString(), | |
93 | "Key error: foo. Detail: received signal " + std::to_string(SIGINT)); | |
94 | st = CancelledFromSignal(SIGINT, "bar"); | |
95 | ASSERT_EQ(SignalFromStatus(st), SIGINT); | |
96 | ASSERT_EQ(st.ToString(), | |
97 | "Cancelled: bar. Detail: received signal " + std::to_string(SIGINT)); | |
98 | ||
99 | st = IOErrorFromErrno(EINVAL, "foo"); | |
100 | ASSERT_EQ(SignalFromStatus(st), 0); | |
101 | } | |
102 | ||
103 | TEST(GetPageSize, Basics) { | |
104 | const auto page_size = GetPageSize(); | |
105 | ASSERT_GE(page_size, 4096); | |
106 | // It's a power of 2 | |
107 | ASSERT_EQ((page_size - 1) & page_size, 0); | |
108 | } | |
109 | ||
110 | TEST(MemoryAdviseWillNeed, Basics) { | |
111 | ASSERT_OK_AND_ASSIGN(auto buf1, AllocateBuffer(8192)); | |
112 | ASSERT_OK_AND_ASSIGN(auto buf2, AllocateBuffer(1024 * 1024)); | |
113 | ||
114 | const auto addr1 = buf1->mutable_data(); | |
115 | const auto size1 = static_cast<size_t>(buf1->size()); | |
116 | const auto addr2 = buf2->mutable_data(); | |
117 | const auto size2 = static_cast<size_t>(buf2->size()); | |
118 | ||
119 | ASSERT_OK(MemoryAdviseWillNeed({})); | |
120 | ASSERT_OK(MemoryAdviseWillNeed({{addr1, size1}, {addr2, size2}})); | |
121 | ASSERT_OK(MemoryAdviseWillNeed({{addr1 + 1, size1 - 1}, {addr2 + 4095, size2 - 4095}})); | |
122 | ASSERT_OK(MemoryAdviseWillNeed({{addr1, 13}, {addr2, 1}})); | |
123 | ASSERT_OK(MemoryAdviseWillNeed({{addr1, 0}, {addr2 + 1, 0}})); | |
124 | ||
125 | // Should probably fail | |
126 | // (but on Windows, MemoryAdviseWillNeed can be a no-op) | |
127 | #ifndef _WIN32 | |
128 | ASSERT_RAISES(IOError, | |
129 | MemoryAdviseWillNeed({{nullptr, std::numeric_limits<size_t>::max()}})); | |
130 | #endif | |
131 | } | |
132 | ||
133 | #if _WIN32 | |
134 | TEST(WinErrorFromStatus, Basics) { | |
135 | Status st; | |
136 | st = Status::OK(); | |
137 | ASSERT_EQ(WinErrorFromStatus(st), 0); | |
138 | st = Status::KeyError("foo"); | |
139 | ASSERT_EQ(WinErrorFromStatus(st), 0); | |
140 | st = Status::IOError("foo"); | |
141 | ASSERT_EQ(WinErrorFromStatus(st), 0); | |
142 | st = StatusFromWinError(ERROR_FILE_NOT_FOUND, StatusCode::KeyError, "foo"); | |
143 | ASSERT_EQ(WinErrorFromStatus(st), ERROR_FILE_NOT_FOUND); | |
144 | st = IOErrorFromWinError(ERROR_ACCESS_DENIED, "foo"); | |
145 | ASSERT_EQ(WinErrorFromStatus(st), ERROR_ACCESS_DENIED); | |
146 | st = IOErrorFromWinError(6789, "foo"); | |
147 | ASSERT_EQ(WinErrorFromStatus(st), 6789); | |
148 | } | |
149 | #endif | |
150 | ||
151 | TEST(PlatformFilename, RoundtripAscii) { | |
152 | PlatformFilename fn; | |
153 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b")); | |
154 | ASSERT_EQ(fn.ToString(), "a/b"); | |
155 | #if _WIN32 | |
156 | ASSERT_EQ(fn.ToNative(), L"a\\b"); | |
157 | #else | |
158 | ASSERT_EQ(fn.ToNative(), "a/b"); | |
159 | #endif | |
160 | } | |
161 | ||
162 | TEST(PlatformFilename, RoundtripUtf8) { | |
163 | PlatformFilename fn; | |
164 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("h\xc3\xa9h\xc3\xa9")); | |
165 | ASSERT_EQ(fn.ToString(), "h\xc3\xa9h\xc3\xa9"); | |
166 | #if _WIN32 | |
167 | ASSERT_EQ(fn.ToNative(), L"h\u00e9h\u00e9"); | |
168 | #else | |
169 | ASSERT_EQ(fn.ToNative(), "h\xc3\xa9h\xc3\xa9"); | |
170 | #endif | |
171 | } | |
172 | ||
173 | #if _WIN32 | |
174 | TEST(PlatformFilename, Separators) { | |
175 | PlatformFilename fn; | |
176 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("C:/foo/bar")); | |
177 | ASSERT_EQ(fn.ToString(), "C:/foo/bar"); | |
178 | ASSERT_EQ(fn.ToNative(), L"C:\\foo\\bar"); | |
179 | ||
180 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("C:\\foo\\bar")); | |
181 | ASSERT_EQ(fn.ToString(), "C:/foo/bar"); | |
182 | ASSERT_EQ(fn.ToNative(), L"C:\\foo\\bar"); | |
183 | } | |
184 | #endif | |
185 | ||
186 | TEST(PlatformFilename, Invalid) { | |
187 | std::string s = "foo"; | |
188 | s += '\x00'; | |
189 | ASSERT_RAISES(Invalid, PlatformFilename::FromString(s)); | |
190 | } | |
191 | ||
192 | TEST(PlatformFilename, Join) { | |
193 | PlatformFilename fn, joined; | |
194 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b")); | |
195 | ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d")); | |
196 | ASSERT_EQ(joined.ToString(), "a/b/c/d"); | |
197 | #if _WIN32 | |
198 | ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d"); | |
199 | #else | |
200 | ASSERT_EQ(joined.ToNative(), "a/b/c/d"); | |
201 | #endif | |
202 | ||
203 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b/")); | |
204 | ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d")); | |
205 | ASSERT_EQ(joined.ToString(), "a/b/c/d"); | |
206 | #if _WIN32 | |
207 | ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d"); | |
208 | #else | |
209 | ASSERT_EQ(joined.ToNative(), "a/b/c/d"); | |
210 | #endif | |
211 | ||
212 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("")); | |
213 | ASSERT_OK_AND_ASSIGN(joined, fn.Join("c/d")); | |
214 | ASSERT_EQ(joined.ToString(), "c/d"); | |
215 | #if _WIN32 | |
216 | ASSERT_EQ(joined.ToNative(), L"c\\d"); | |
217 | #else | |
218 | ASSERT_EQ(joined.ToNative(), "c/d"); | |
219 | #endif | |
220 | ||
221 | #if _WIN32 | |
222 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a\\b")); | |
223 | ASSERT_OK_AND_ASSIGN(joined, fn.Join("c\\d")); | |
224 | ASSERT_EQ(joined.ToString(), "a/b/c/d"); | |
225 | ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d"); | |
226 | ||
227 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a\\b\\")); | |
228 | ASSERT_OK_AND_ASSIGN(joined, fn.Join("c\\d")); | |
229 | ASSERT_EQ(joined.ToString(), "a/b/c/d"); | |
230 | ASSERT_EQ(joined.ToNative(), L"a\\b\\c\\d"); | |
231 | #endif | |
232 | } | |
233 | ||
234 | TEST(PlatformFilename, JoinInvalid) { | |
235 | PlatformFilename fn; | |
236 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("a/b")); | |
237 | std::string s = "foo"; | |
238 | s += '\x00'; | |
239 | ASSERT_RAISES(Invalid, fn.Join(s)); | |
240 | } | |
241 | ||
242 | TEST(PlatformFilename, Parent) { | |
243 | PlatformFilename fn; | |
244 | ||
245 | // Relative | |
246 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/cd")); | |
247 | ASSERT_EQ(fn.ToString(), "ab/cd"); | |
248 | fn = fn.Parent(); | |
249 | ASSERT_EQ(fn.ToString(), "ab"); | |
250 | fn = fn.Parent(); | |
251 | ASSERT_EQ(fn.ToString(), "ab"); | |
252 | #if _WIN32 | |
253 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/cd\\ef")); | |
254 | ASSERT_EQ(fn.ToString(), "ab/cd/ef"); | |
255 | fn = fn.Parent(); | |
256 | ASSERT_EQ(fn.ToString(), "ab/cd"); | |
257 | fn = fn.Parent(); | |
258 | ASSERT_EQ(fn.ToString(), "ab"); | |
259 | fn = fn.Parent(); | |
260 | ASSERT_EQ(fn.ToString(), "ab"); | |
261 | #endif | |
262 | ||
263 | // Absolute | |
264 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef")); | |
265 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef"); | |
266 | fn = fn.Parent(); | |
267 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
268 | fn = fn.Parent(); | |
269 | ASSERT_EQ(fn.ToString(), "/ab"); | |
270 | fn = fn.Parent(); | |
271 | ASSERT_EQ(fn.ToString(), "/"); | |
272 | fn = fn.Parent(); | |
273 | ASSERT_EQ(fn.ToString(), "/"); | |
274 | #if _WIN32 | |
275 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd/ef")); | |
276 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef"); | |
277 | fn = fn.Parent(); | |
278 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
279 | fn = fn.Parent(); | |
280 | ASSERT_EQ(fn.ToString(), "/ab"); | |
281 | fn = fn.Parent(); | |
282 | ASSERT_EQ(fn.ToString(), "/"); | |
283 | fn = fn.Parent(); | |
284 | ASSERT_EQ(fn.ToString(), "/"); | |
285 | #endif | |
286 | ||
287 | // Empty | |
288 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("")); | |
289 | ASSERT_EQ(fn.ToString(), ""); | |
290 | fn = fn.Parent(); | |
291 | ASSERT_EQ(fn.ToString(), ""); | |
292 | ||
293 | // Multiple separators, relative | |
294 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab//cd///ef")); | |
295 | ASSERT_EQ(fn.ToString(), "ab//cd///ef"); | |
296 | fn = fn.Parent(); | |
297 | ASSERT_EQ(fn.ToString(), "ab//cd"); | |
298 | fn = fn.Parent(); | |
299 | ASSERT_EQ(fn.ToString(), "ab"); | |
300 | fn = fn.Parent(); | |
301 | ASSERT_EQ(fn.ToString(), "ab"); | |
302 | #if _WIN32 | |
303 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\\\cd\\\\\\ef")); | |
304 | ASSERT_EQ(fn.ToString(), "ab//cd///ef"); | |
305 | fn = fn.Parent(); | |
306 | ASSERT_EQ(fn.ToString(), "ab//cd"); | |
307 | fn = fn.Parent(); | |
308 | ASSERT_EQ(fn.ToString(), "ab"); | |
309 | fn = fn.Parent(); | |
310 | ASSERT_EQ(fn.ToString(), "ab"); | |
311 | #endif | |
312 | ||
313 | // Multiple separators, absolute | |
314 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("//ab//cd///ef")); | |
315 | ASSERT_EQ(fn.ToString(), "//ab//cd///ef"); | |
316 | fn = fn.Parent(); | |
317 | ASSERT_EQ(fn.ToString(), "//ab//cd"); | |
318 | fn = fn.Parent(); | |
319 | ASSERT_EQ(fn.ToString(), "//ab"); | |
320 | fn = fn.Parent(); | |
321 | ASSERT_EQ(fn.ToString(), "//"); | |
322 | fn = fn.Parent(); | |
323 | ASSERT_EQ(fn.ToString(), "//"); | |
324 | #if _WIN32 | |
325 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\\\ab\\cd\\ef")); | |
326 | ASSERT_EQ(fn.ToString(), "//ab/cd/ef"); | |
327 | fn = fn.Parent(); | |
328 | ASSERT_EQ(fn.ToString(), "//ab/cd"); | |
329 | fn = fn.Parent(); | |
330 | ASSERT_EQ(fn.ToString(), "//ab"); | |
331 | fn = fn.Parent(); | |
332 | ASSERT_EQ(fn.ToString(), "//"); | |
333 | fn = fn.Parent(); | |
334 | ASSERT_EQ(fn.ToString(), "//"); | |
335 | #endif | |
336 | ||
337 | // Trailing slashes | |
338 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef/")); | |
339 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef/"); | |
340 | fn = fn.Parent(); | |
341 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
342 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("/ab/cd/ef//")); | |
343 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef//"); | |
344 | fn = fn.Parent(); | |
345 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
346 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab/")); | |
347 | ASSERT_EQ(fn.ToString(), "ab/"); | |
348 | fn = fn.Parent(); | |
349 | ASSERT_EQ(fn.ToString(), "ab/"); | |
350 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab//")); | |
351 | ASSERT_EQ(fn.ToString(), "ab//"); | |
352 | fn = fn.Parent(); | |
353 | ASSERT_EQ(fn.ToString(), "ab//"); | |
354 | #if _WIN32 | |
355 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd\\ef\\")); | |
356 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef/"); | |
357 | fn = fn.Parent(); | |
358 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
359 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("\\ab\\cd\\ef\\\\")); | |
360 | ASSERT_EQ(fn.ToString(), "/ab/cd/ef//"); | |
361 | fn = fn.Parent(); | |
362 | ASSERT_EQ(fn.ToString(), "/ab/cd"); | |
363 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\")); | |
364 | ASSERT_EQ(fn.ToString(), "ab/"); | |
365 | fn = fn.Parent(); | |
366 | ASSERT_EQ(fn.ToString(), "ab/"); | |
367 | ASSERT_OK_AND_ASSIGN(fn, PlatformFilename::FromString("ab\\\\")); | |
368 | ASSERT_EQ(fn.ToString(), "ab//"); | |
369 | fn = fn.Parent(); | |
370 | ASSERT_EQ(fn.ToString(), "ab//"); | |
371 | #endif | |
372 | } | |
373 | ||
374 | TEST(CreateDirDeleteDir, Basics) { | |
375 | std::unique_ptr<TemporaryDir> temp_dir; | |
376 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("deletedirtest-")); | |
377 | const std::string BASE = | |
378 | temp_dir->path().Join("xxx-io-util-test-dir2").ValueOrDie().ToString(); | |
379 | bool created, deleted; | |
380 | PlatformFilename parent, child, child_file; | |
381 | ||
382 | ASSERT_OK_AND_ASSIGN(parent, PlatformFilename::FromString(BASE)); | |
383 | ASSERT_EQ(parent.ToString(), BASE); | |
384 | ||
385 | // Make sure the directory doesn't exist already | |
386 | ARROW_UNUSED(DeleteDirTree(parent)); | |
387 | ||
388 | AssertNotExists(parent); | |
389 | ||
390 | ASSERT_OK_AND_ASSIGN(created, CreateDir(parent)); | |
391 | ASSERT_TRUE(created); | |
392 | AssertExists(parent); | |
393 | ASSERT_OK_AND_ASSIGN(created, CreateDir(parent)); | |
394 | ASSERT_FALSE(created); // already exists | |
395 | AssertExists(parent); | |
396 | ||
397 | ASSERT_OK_AND_ASSIGN(child, PlatformFilename::FromString(BASE + "/some-child")); | |
398 | ASSERT_OK_AND_ASSIGN(created, CreateDir(child)); | |
399 | ASSERT_TRUE(created); | |
400 | AssertExists(child); | |
401 | ||
402 | ASSERT_OK_AND_ASSIGN(child_file, PlatformFilename::FromString(BASE + "/some-file")); | |
403 | TouchFile(child_file); | |
404 | EXPECT_RAISES_WITH_MESSAGE_THAT( | |
405 | IOError, ::testing::HasSubstr("non-directory entry exists"), CreateDir(child_file)); | |
406 | ||
407 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent)); | |
408 | ASSERT_TRUE(deleted); | |
409 | AssertNotExists(parent); | |
410 | AssertNotExists(child); | |
411 | ||
412 | // Parent is deleted, cannot create child again | |
413 | ASSERT_RAISES(IOError, CreateDir(child)); | |
414 | ||
415 | // It's not an error to call DeleteDirTree on a nonexistent path. | |
416 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent)); | |
417 | ASSERT_FALSE(deleted); | |
418 | // ... unless asked so | |
419 | auto status = DeleteDirTree(parent, /*allow_not_found=*/false).status(); | |
420 | ASSERT_RAISES(IOError, status); | |
421 | #ifdef _WIN32 | |
422 | ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND); | |
423 | #else | |
424 | ASSERT_EQ(ErrnoFromStatus(status), ENOENT); | |
425 | #endif | |
426 | } | |
427 | ||
428 | TEST(DeleteDirContents, Basics) { | |
429 | std::unique_ptr<TemporaryDir> temp_dir; | |
430 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("deletedirtest-")); | |
431 | const std::string BASE = | |
432 | temp_dir->path().Join("xxx-io-util-test-dir2").ValueOrDie().ToString(); | |
433 | bool created, deleted; | |
434 | PlatformFilename parent, child1, child2; | |
435 | ||
436 | ASSERT_OK_AND_ASSIGN(parent, PlatformFilename::FromString(BASE)); | |
437 | ASSERT_EQ(parent.ToString(), BASE); | |
438 | ||
439 | // Make sure the directory doesn't exist already | |
440 | ARROW_UNUSED(DeleteDirTree(parent)); | |
441 | ||
442 | AssertNotExists(parent); | |
443 | ||
444 | // Create the parent, a child dir and a child file | |
445 | ASSERT_OK_AND_ASSIGN(created, CreateDir(parent)); | |
446 | ASSERT_TRUE(created); | |
447 | ASSERT_OK_AND_ASSIGN(child1, PlatformFilename::FromString(BASE + "/child-dir")); | |
448 | ASSERT_OK_AND_ASSIGN(child2, PlatformFilename::FromString(BASE + "/child-file")); | |
449 | ASSERT_OK_AND_ASSIGN(created, CreateDir(child1)); | |
450 | ASSERT_TRUE(created); | |
451 | TouchFile(child2); | |
452 | AssertExists(child1); | |
453 | AssertExists(child2); | |
454 | ||
455 | // Cannot call DeleteDirContents on a file | |
456 | ASSERT_RAISES(IOError, DeleteDirContents(child2)); | |
457 | AssertExists(child2); | |
458 | ||
459 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(parent)); | |
460 | ASSERT_TRUE(deleted); | |
461 | AssertExists(parent); | |
462 | AssertNotExists(child1); | |
463 | AssertNotExists(child2); | |
464 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(parent)); | |
465 | ASSERT_TRUE(deleted); | |
466 | AssertExists(parent); | |
467 | ||
468 | // It's not an error to call DeleteDirContents on a nonexistent path. | |
469 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(child1)); | |
470 | ASSERT_FALSE(deleted); | |
471 | // ... unless asked so | |
472 | auto status = DeleteDirContents(child1, /*allow_not_found=*/false).status(); | |
473 | ASSERT_RAISES(IOError, status); | |
474 | #ifdef _WIN32 | |
475 | ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND); | |
476 | #else | |
477 | ASSERT_EQ(ErrnoFromStatus(status), ENOENT); | |
478 | #endif | |
479 | ||
480 | // Now actually delete the test directory | |
481 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(parent)); | |
482 | ASSERT_TRUE(deleted); | |
483 | } | |
484 | ||
485 | TEST(TemporaryDir, Basics) { | |
486 | std::unique_ptr<TemporaryDir> temp_dir; | |
487 | PlatformFilename fn; | |
488 | ||
489 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("some-prefix-")); | |
490 | fn = temp_dir->path(); | |
491 | // Path has a trailing separator, for convenience | |
492 | ASSERT_EQ(fn.ToString().back(), '/'); | |
493 | #if defined(_WIN32) | |
494 | ASSERT_EQ(fn.ToNative().back(), L'\\'); | |
495 | #else | |
496 | ASSERT_EQ(fn.ToNative().back(), '/'); | |
497 | #endif | |
498 | AssertExists(fn); | |
499 | ASSERT_NE(fn.ToString().find("some-prefix-"), std::string::npos); | |
500 | ||
501 | // Create child contents to check that they're cleaned up at the end | |
502 | #if defined(_WIN32) | |
503 | PlatformFilename child(fn.ToNative() + L"some-child"); | |
504 | #else | |
505 | PlatformFilename child(fn.ToNative() + "some-child"); | |
506 | #endif | |
507 | ASSERT_OK(CreateDir(child)); | |
508 | AssertExists(child); | |
509 | ||
510 | temp_dir.reset(); | |
511 | AssertNotExists(fn); | |
512 | AssertNotExists(child); | |
513 | } | |
514 | ||
515 | TEST(CreateDirTree, Basics) { | |
516 | std::unique_ptr<TemporaryDir> temp_dir; | |
517 | PlatformFilename fn; | |
518 | bool created; | |
519 | ||
520 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-")); | |
521 | ||
522 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/CD")); | |
523 | ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn)); | |
524 | ASSERT_TRUE(created); | |
525 | ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn)); | |
526 | ASSERT_FALSE(created); | |
527 | ||
528 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB")); | |
529 | ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn)); | |
530 | ASSERT_FALSE(created); | |
531 | ||
532 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("EF")); | |
533 | ASSERT_OK_AND_ASSIGN(created, CreateDirTree(fn)); | |
534 | ASSERT_TRUE(created); | |
535 | ||
536 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/file")); | |
537 | TouchFile(fn); | |
538 | EXPECT_RAISES_WITH_MESSAGE_THAT( | |
539 | IOError, ::testing::HasSubstr("non-directory entry exists"), CreateDirTree(fn)); | |
540 | ||
541 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/file/sub")); | |
542 | ASSERT_RAISES(IOError, CreateDirTree(fn)); | |
543 | } | |
544 | ||
545 | TEST(ListDir, Basics) { | |
546 | std::unique_ptr<TemporaryDir> temp_dir; | |
547 | PlatformFilename fn; | |
548 | std::vector<PlatformFilename> entries; | |
549 | ||
550 | auto check_entries = [](const std::vector<PlatformFilename>& entries, | |
551 | std::vector<std::string> expected) -> void { | |
552 | std::vector<std::string> actual(entries.size()); | |
553 | std::transform(entries.begin(), entries.end(), actual.begin(), | |
554 | [](const PlatformFilename& fn) { return fn.ToString(); }); | |
555 | // Sort results for deterministic testing | |
556 | std::sort(actual.begin(), actual.end()); | |
557 | ASSERT_EQ(actual, expected); | |
558 | }; | |
559 | ||
560 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-")); | |
561 | ||
562 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/CD")); | |
563 | ASSERT_OK(CreateDirTree(fn)); | |
564 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/EF/GH")); | |
565 | ASSERT_OK(CreateDirTree(fn)); | |
566 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/ghi.txt")); | |
567 | TouchFile(fn); | |
568 | ||
569 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB")); | |
570 | ASSERT_OK_AND_ASSIGN(entries, ListDir(fn)); | |
571 | ASSERT_EQ(entries.size(), 3); | |
572 | check_entries(entries, {"CD", "EF", "ghi.txt"}); | |
573 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/EF/GH")); | |
574 | ASSERT_OK_AND_ASSIGN(entries, ListDir(fn)); | |
575 | check_entries(entries, {}); | |
576 | ||
577 | // Errors | |
578 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("nonexistent")); | |
579 | ASSERT_RAISES(IOError, ListDir(fn)); | |
580 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("AB/ghi.txt")); | |
581 | ASSERT_RAISES(IOError, ListDir(fn)); | |
582 | } | |
583 | ||
584 | TEST(DeleteFile, Basics) { | |
585 | std::unique_ptr<TemporaryDir> temp_dir; | |
586 | PlatformFilename fn; | |
587 | bool deleted; | |
588 | ||
589 | ASSERT_OK_AND_ASSIGN(temp_dir, TemporaryDir::Make("io-util-test-")); | |
590 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("test-file")); | |
591 | ||
592 | AssertNotExists(fn); | |
593 | TouchFile(fn); | |
594 | AssertExists(fn); | |
595 | ASSERT_OK_AND_ASSIGN(deleted, DeleteFile(fn)); | |
596 | ASSERT_TRUE(deleted); | |
597 | AssertNotExists(fn); | |
598 | ASSERT_OK_AND_ASSIGN(deleted, DeleteFile(fn)); | |
599 | ASSERT_FALSE(deleted); | |
600 | AssertNotExists(fn); | |
601 | auto status = DeleteFile(fn, /*allow_not_found=*/false).status(); | |
602 | ASSERT_RAISES(IOError, status); | |
603 | #ifdef _WIN32 | |
604 | ASSERT_EQ(WinErrorFromStatus(status), ERROR_FILE_NOT_FOUND); | |
605 | #else | |
606 | ASSERT_EQ(ErrnoFromStatus(status), ENOENT); | |
607 | #endif | |
608 | ||
609 | // Cannot call DeleteFile on directory | |
610 | ASSERT_OK_AND_ASSIGN(fn, temp_dir->path().Join("test-temp_dir")); | |
611 | ASSERT_OK(CreateDir(fn)); | |
612 | AssertExists(fn); | |
613 | ASSERT_RAISES(IOError, DeleteFile(fn)); | |
614 | } | |
615 | ||
616 | #ifndef __APPLE__ | |
617 | TEST(FileUtils, LongPaths) { | |
618 | // ARROW-8477: check using long file paths under Windows (> 260 characters). | |
619 | bool created, deleted; | |
620 | #ifdef _WIN32 | |
621 | const char* kRegKeyName = R"(SYSTEM\CurrentControlSet\Control\FileSystem)"; | |
622 | const char* kRegValueName = "LongPathsEnabled"; | |
623 | DWORD value = 0; | |
624 | DWORD size = sizeof(value); | |
625 | LSTATUS status = RegGetValueA(HKEY_LOCAL_MACHINE, kRegKeyName, kRegValueName, | |
626 | RRF_RT_REG_DWORD, NULL, &value, &size); | |
627 | bool test_long_paths = (status == ERROR_SUCCESS && value == 1); | |
628 | if (!test_long_paths) { | |
629 | ARROW_LOG(WARNING) | |
630 | << "Tests for accessing files with long path names have been disabled. " | |
631 | << "To enable these tests, set the value of " << kRegValueName | |
632 | << " in registry key \\HKEY_LOCAL_MACHINE\\" << kRegKeyName | |
633 | << " to 1 on the test host."; | |
634 | return; | |
635 | } | |
636 | #endif | |
637 | ||
638 | const std::string BASE = "xxx-io-util-test-dir-long"; | |
639 | PlatformFilename base_path, long_path, long_filename; | |
640 | int fd = -1; | |
641 | std::stringstream fs; | |
642 | fs << BASE; | |
643 | for (int i = 0; i < 64; ++i) { | |
644 | fs << "/123456789ABCDEF"; | |
645 | } | |
646 | ASSERT_OK_AND_ASSIGN(base_path, | |
647 | PlatformFilename::FromString(BASE)); // long_path length > 1024 | |
648 | ASSERT_OK_AND_ASSIGN( | |
649 | long_path, PlatformFilename::FromString(fs.str())); // long_path length > 1024 | |
650 | ASSERT_OK_AND_ASSIGN(created, CreateDirTree(long_path)); | |
651 | ASSERT_TRUE(created); | |
652 | AssertExists(long_path); | |
653 | ASSERT_OK_AND_ASSIGN(long_filename, | |
654 | PlatformFilename::FromString(fs.str() + "/file.txt")); | |
655 | TouchFile(long_filename); | |
656 | AssertExists(long_filename); | |
657 | fd = -1; | |
658 | ASSERT_OK_AND_ASSIGN(fd, FileOpenReadable(long_filename)); | |
659 | ASSERT_OK(FileClose(fd)); | |
660 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirContents(long_path)); | |
661 | ASSERT_TRUE(deleted); | |
662 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(long_path)); | |
663 | ASSERT_TRUE(deleted); | |
664 | ||
665 | // Now delete the whole test directory tree | |
666 | ASSERT_OK_AND_ASSIGN(deleted, DeleteDirTree(base_path)); | |
667 | ASSERT_TRUE(deleted); | |
668 | } | |
669 | #endif | |
670 | ||
671 | static std::atomic<int> signal_received; | |
672 | ||
673 | static void handle_signal(int signum) { | |
674 | ReinstateSignalHandler(signum, &handle_signal); | |
675 | signal_received.store(signum); | |
676 | } | |
677 | ||
678 | TEST(SendSignal, Generic) { | |
679 | signal_received.store(0); | |
680 | SignalHandlerGuard guard(SIGINT, &handle_signal); | |
681 | ||
682 | ASSERT_EQ(signal_received.load(), 0); | |
683 | ASSERT_OK(SendSignal(SIGINT)); | |
684 | BusyWait(1.0, [&]() { return signal_received.load() != 0; }); | |
685 | ASSERT_EQ(signal_received.load(), SIGINT); | |
686 | ||
687 | // Re-try (exercise ReinstateSignalHandler) | |
688 | signal_received.store(0); | |
689 | ASSERT_OK(SendSignal(SIGINT)); | |
690 | BusyWait(1.0, [&]() { return signal_received.load() != 0; }); | |
691 | ASSERT_EQ(signal_received.load(), SIGINT); | |
692 | } | |
693 | ||
694 | TEST(SendSignal, ToThread) { | |
695 | #ifdef _WIN32 | |
696 | uint64_t dummy_thread_id = 42; | |
697 | ASSERT_RAISES(NotImplemented, SendSignalToThread(SIGINT, dummy_thread_id)); | |
698 | #else | |
699 | // Have to use a C-style cast because pthread_t can be a pointer *or* integer type | |
700 | uint64_t thread_id = (uint64_t)(pthread_self()); // NOLINT readability-casting | |
701 | signal_received.store(0); | |
702 | SignalHandlerGuard guard(SIGINT, &handle_signal); | |
703 | ||
704 | ASSERT_EQ(signal_received.load(), 0); | |
705 | ASSERT_OK(SendSignalToThread(SIGINT, thread_id)); | |
706 | BusyWait(1.0, [&]() { return signal_received.load() != 0; }); | |
707 | ||
708 | ASSERT_EQ(signal_received.load(), SIGINT); | |
709 | #endif | |
710 | } | |
711 | ||
712 | } // namespace internal | |
713 | } // namespace arrow |