/Users/deen/code/yugabyte-db/src/yb/util/env-test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | // |
18 | | // The following only applies to changes made to this file as part of YugaByte development. |
19 | | // |
20 | | // Portions Copyright (c) YugaByte, Inc. |
21 | | // |
22 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
23 | | // in compliance with the License. You may obtain a copy of the License at |
24 | | // |
25 | | // http://www.apache.org/licenses/LICENSE-2.0 |
26 | | // |
27 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
28 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
29 | | // or implied. See the License for the specific language governing permissions and limitations |
30 | | // under the License. |
31 | | // |
32 | | |
33 | | #include <fcntl.h> |
34 | | #include <sys/types.h> |
35 | | |
36 | | #include <memory> |
37 | | #include <string> |
38 | | |
39 | | #include <glog/logging.h> |
40 | | #include <gtest/gtest.h> |
41 | | |
42 | | #include "yb/gutil/bind.h" |
43 | | #include "yb/gutil/strings/substitute.h" |
44 | | #include "yb/gutil/strings/util.h" |
45 | | #include "yb/util/alignment.h" |
46 | | #include "yb/util/stol_utils.h" |
47 | | #include "yb/util/crc.h" |
48 | | #include "yb/util/env.h" |
49 | | #include "yb/util/env_util.h" |
50 | | #include "yb/util/memenv/memenv.h" |
51 | | #include "yb/util/os-util.h" |
52 | | #include "yb/util/random.h" |
53 | | #include "yb/util/random_util.h" |
54 | | #include "yb/util/path_util.h" |
55 | | #include "yb/util/status.h" |
56 | | #include "yb/util/stopwatch.h" |
57 | | #include "yb/util/test_macros.h" |
58 | | #include "yb/util/test_util.h" |
59 | | |
60 | | DECLARE_int32(o_direct_block_size_bytes); |
61 | | DECLARE_bool(TEST_simulate_fs_without_fallocate); |
62 | | |
63 | | #if !defined(__APPLE__) |
64 | | #include <linux/falloc.h> |
65 | | #endif // !defined(__APPLE__) |
66 | | // Copied from falloc.h. Useful for older kernels that lack support for |
67 | | // hole punching; fallocate(2) will return EOPNOTSUPP. |
68 | | #ifndef FALLOC_FL_KEEP_SIZE |
69 | | #define FALLOC_FL_KEEP_SIZE 0x01 /* default is extend size */ |
70 | | #endif |
71 | | #ifndef FALLOC_FL_PUNCH_HOLE |
72 | | #define FALLOC_FL_PUNCH_HOLE 0x02 /* de-allocates range */ |
73 | | #endif |
74 | | |
75 | | using namespace std::placeholders; |
76 | | |
77 | | namespace yb { |
78 | | |
79 | | using std::shared_ptr; |
80 | | using std::string; |
81 | | using std::vector; |
82 | | |
83 | | static const uint64_t kOneMb = 1024 * 1024; |
84 | | |
85 | | class TestEnv : public YBTest, public ::testing::WithParamInterface<bool> { |
86 | | public: |
87 | 21 | void SetUp() override { |
88 | 21 | YBTest::SetUp(); |
89 | 21 | CheckFallocateSupport(); |
90 | 21 | } |
91 | | |
92 | | // Verify that fallocate() is supported in the test directory. |
93 | | // Some local file systems like ext3 do not support it, and we don't |
94 | | // want to fail tests on those systems. |
95 | | // |
96 | | // Sets fallocate_supported_ based on the result. |
97 | 21 | void CheckFallocateSupport() { |
98 | 21 | static bool checked = false; |
99 | 21 | if (checked) return; |
100 | | |
101 | 21 | if (FLAGS_TEST_simulate_fs_without_fallocate) { |
102 | 0 | checked = true; |
103 | 0 | return; |
104 | 0 | } |
105 | | |
106 | | #if defined(__linux__) |
107 | | int fd = creat(GetTestPath("check-fallocate").c_str(), S_IWUSR); |
108 | | PCHECK(fd >= 0); |
109 | | int err = fallocate(fd, 0, 0, 4096); |
110 | | if (err != 0) { |
111 | | PCHECK(errno == ENOTSUP); |
112 | | } else { |
113 | | fallocate_supported_ = true; |
114 | | |
115 | | err = fallocate(fd, FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE, |
116 | | 1024, 1024); |
117 | | if (err != 0) { |
118 | | PCHECK(errno == ENOTSUP); |
119 | | } else { |
120 | | fallocate_punch_hole_supported_ = true; |
121 | | } |
122 | | } |
123 | | |
124 | | close(fd); |
125 | | #endif |
126 | | |
127 | 21 | checked = true; |
128 | 21 | } |
129 | | |
130 | | protected: |
131 | | |
132 | 12 | void VerifyTestData(const Slice& read_data, size_t offset) { |
133 | 21.5M | for (size_t i = 0; i < read_data.size(); i++) { |
134 | 21.5M | size_t file_offset = offset + i; |
135 | 43.0M | ASSERT_EQ((file_offset * 31) & 0xff, read_data[i]) << "failed at " << i; |
136 | 21.5M | } |
137 | 12 | } |
138 | | |
139 | | void VerifyChecksumsMatch(const string file_path, size_t file_size, uint64_t expected_checksum, |
140 | 1 | const crc::Crc* crc32c) { |
141 | 1 | shared_ptr<RandomAccessFile> raf; |
142 | 1 | ASSERT_OK(env_util::OpenFileForRandom(env_.get(), file_path, &raf)); |
143 | 1 | Slice slice; |
144 | 1 | std::unique_ptr<uint8_t[]> scratch(new uint8_t[file_size]); |
145 | 1 | ASSERT_OK(env_util::ReadFully(raf.get(), 0, file_size, &slice, scratch.get())); |
146 | 1 | ASSERT_EQ(file_size, slice.size()); |
147 | 1 | uint64_t file_checksum = 0; |
148 | 1 | crc32c->Compute(slice.data(), slice.size(), &file_checksum); |
149 | 2 | ASSERT_EQ(file_checksum, expected_checksum) << "File checksum didn't match expected checksum"; |
150 | 1 | } |
151 | | |
152 | | void MakeVectors(size_t num_slices, size_t slice_size, size_t num_iterations, |
153 | 2 | std::unique_ptr<faststring[]>* data, vector<vector<Slice > >* vec) { |
154 | 2 | data->reset(new faststring[num_iterations * num_slices]); |
155 | 2 | vec->resize(num_iterations); |
156 | | |
157 | 2 | int data_idx = 0; |
158 | 2 | int byte_idx = 0; |
159 | 12 | for (size_t vec_idx = 0; vec_idx < num_iterations; vec_idx++) { |
160 | 10 | vector<Slice>& iter_vec = vec->at(vec_idx); |
161 | 10 | iter_vec.resize(num_slices); |
162 | 20.0k | for (size_t i = 0; i < num_slices; i++) { |
163 | 20.0k | (*data)[data_idx].resize(slice_size); |
164 | 20.5M | for (size_t j = 0; j < slice_size; j++) { |
165 | 20.4M | (*data)[data_idx][j] = (byte_idx * 31) & 0xff; |
166 | 20.4M | ++byte_idx; |
167 | 20.4M | } |
168 | 20.0k | iter_vec[i]= Slice((*data)[data_idx]); |
169 | 20.0k | ++data_idx; |
170 | 20.0k | } |
171 | 10 | } |
172 | 2 | } |
173 | | |
174 | 11 | void ReadAndVerifyTestData(RandomAccessFile* raf, size_t offset, size_t n) { |
175 | 11 | std::unique_ptr<uint8_t[]> scratch(new uint8_t[n]); |
176 | 11 | Slice s; |
177 | 11 | ASSERT_OK(env_util::ReadFully(raf, offset, n, &s, |
178 | 11 | scratch.get())); |
179 | 11 | ASSERT_EQ(n, s.size()); |
180 | 11 | ASSERT_NO_FATALS(VerifyTestData(s, offset)); |
181 | 11 | } |
182 | | |
183 | | void TestAppendVector(size_t num_slices, size_t slice_size, size_t iterations, |
184 | 2 | bool fast, bool pre_allocate, const WritableFileOptions& opts) { |
185 | 2 | const string kTestPath = GetTestPath("test_env_appendvec_read_append"); |
186 | 2 | shared_ptr<WritableFile> file; |
187 | 2 | ASSERT_OK(env_util::OpenFileForWrite(opts, env_.get(), kTestPath, &file)); |
188 | | |
189 | 2 | if (pre_allocate) { |
190 | 0 | ASSERT_OK(file->PreAllocate(num_slices * slice_size * iterations)); |
191 | 0 | ASSERT_OK(file->Sync()); |
192 | 0 | } |
193 | | |
194 | 2 | std::unique_ptr<faststring[]> data; |
195 | 2 | vector<vector<Slice> > input; |
196 | | |
197 | 2 | MakeVectors(num_slices, slice_size, iterations, &data, &input); |
198 | | |
199 | 2 | shared_ptr<RandomAccessFile> raf; |
200 | | |
201 | 2 | if (!fast) { |
202 | 0 | ASSERT_OK(env_util::OpenFileForRandom(env_.get(), kTestPath, &raf)); |
203 | 0 | } |
204 | | |
205 | 2 | srand(123); |
206 | | |
207 | 2 | const string test_descr = strings::Substitute( |
208 | 2 | "appending a vector of slices(number of slices=$0,size of slice=$1 b) $2 times", |
209 | 2 | num_slices, slice_size, iterations); |
210 | 2 | LOG_TIMING(INFO, test_descr) { |
211 | 12 | for (size_t i = 0; i < iterations; i++) { |
212 | 10 | if (fast || random() % 2) { |
213 | 10 | ASSERT_OK(file->AppendVector(input[i])); |
214 | 0 | } else { |
215 | 0 | for (const Slice& slice : input[i]) { |
216 | 0 | ASSERT_OK(file->Append(slice)); |
217 | 0 | } |
218 | 0 | } |
219 | 10 | if (!fast) { |
220 | | // Verify as write. Note: this requires that file is pre-allocated, otherwise |
221 | | // the ReadFully() fails with EINVAL. |
222 | 0 | if (opts.o_direct) { |
223 | 0 | ASSERT_OK(file->Sync()); |
224 | 0 | } |
225 | 0 | ASSERT_NO_FATALS(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i, |
226 | 0 | num_slices * slice_size)); |
227 | 0 | } |
228 | 10 | } |
229 | 2 | } |
230 | | |
231 | | // Verify the entire file |
232 | 2 | ASSERT_OK(file->Close()); |
233 | | |
234 | 2 | if (fast) { |
235 | 2 | ASSERT_OK(env_util::OpenFileForRandom(env_.get(), kTestPath, &raf)); |
236 | 2 | } |
237 | 12 | for (size_t i = 0; i < iterations; i++) { |
238 | 10 | ASSERT_NO_FATALS(ReadAndVerifyTestData(raf.get(), num_slices * slice_size * i, |
239 | 10 | num_slices * slice_size)); |
240 | 10 | } |
241 | 2 | } |
242 | | |
243 | 1 | void TestAppendRandomData(bool pre_allocate, const WritableFileOptions& opts) { |
244 | 1 | const string kTestPath = GetTestPath("test_env_append_random_read_append"); |
245 | 1 | const uint64_t kMaxFileSize = 64 * 1024 * 1024; |
246 | 1 | shared_ptr<WritableFile> file; |
247 | 1 | ASSERT_OK(env_util::OpenFileForWrite(opts, env_.get(), kTestPath, &file)); |
248 | | |
249 | 1 | if (pre_allocate) { |
250 | 1 | ASSERT_OK(file->PreAllocate(kMaxFileSize)); |
251 | 1 | ASSERT_OK(file->Sync()); |
252 | 1 | } |
253 | | |
254 | 1 | Random rnd(SeedRandom()); |
255 | | |
256 | 1 | size_t total_size = 0; |
257 | 1 | const int kBufSize = (IOV_MAX + 10) * FLAGS_o_direct_block_size_bytes; |
258 | 1 | char buf[kBufSize]; |
259 | 1 | crc::Crc* crc32c = crc::GetCrc32cInstance(); |
260 | 1 | uint64_t actual_checksum = 0; |
261 | | |
262 | 163 | while (true) { |
263 | 163 | auto i = rnd.Uniform(10); |
264 | 163 | size_t slice_size; |
265 | 163 | if (i < 4) { |
266 | | // 40% of the time pick a size between 1 and FLAGS_o_direct_block_size_bytes. |
267 | 60 | slice_size = rnd.Uniform(FLAGS_o_direct_block_size_bytes) + 1; |
268 | 103 | } else if (i >= 4 && i < 8) { |
269 | | // 40% of the time pick a size between block_size and 10*FLAGS_o_direct_block_size_bytes |
270 | | // that is a multiple of FLAGS_o_direct_block_size_bytes. |
271 | 64 | slice_size = (rnd.Uniform(10) + 1) * FLAGS_o_direct_block_size_bytes; |
272 | 39 | } else if (i == 8) { |
273 | | // 10% of the time pick a size greater than the IOV_MAX * FLAGS_o_direct_block_size_bytes. |
274 | 16 | slice_size = IOV_MAX * FLAGS_o_direct_block_size_bytes + |
275 | 16 | rnd.Uniform(10 * FLAGS_o_direct_block_size_bytes); |
276 | 23 | } else { |
277 | | // 10% of the time pick a size such that the file size after writing this slice is a |
278 | | // multiple of FLAGS_o_direct_block_size_bytes. |
279 | 23 | auto bytes_needed = align_up(total_size, FLAGS_o_direct_block_size_bytes) - total_size; |
280 | 23 | slice_size = FLAGS_o_direct_block_size_bytes + bytes_needed; |
281 | 23 | } |
282 | 163 | if (total_size + slice_size > kMaxFileSize) { |
283 | 1 | break; |
284 | 1 | } |
285 | 162 | total_size += slice_size; |
286 | | |
287 | 162 | RandomString(buf, slice_size, &rnd); |
288 | 162 | auto slice = Slice(buf, slice_size); |
289 | 162 | ASSERT_OK(file->Append(slice)); |
290 | 162 | ASSERT_EQ(total_size, file->Size()); |
291 | | |
292 | | // Compute a rolling checksum over the two byte arrays (size, body). |
293 | 162 | crc32c->Compute(slice.data(), slice.size(), &actual_checksum); |
294 | | |
295 | 162 | if (rnd.Uniform(5) == 0) { |
296 | 39 | ASSERT_OK(file->Sync()); |
297 | 39 | ASSERT_EQ(total_size, file->Size()); |
298 | 39 | } |
299 | 162 | } |
300 | | |
301 | | // Verify the entire file |
302 | 1 | ASSERT_OK(file->Close()); |
303 | 1 | ASSERT_NO_FATALS(VerifyChecksumsMatch(kTestPath, total_size, actual_checksum, crc32c)); |
304 | 1 | } |
305 | | |
306 | | static bool fallocate_supported_; |
307 | | static bool fallocate_punch_hole_supported_; |
308 | | }; |
309 | | |
310 | | bool TestEnv::fallocate_supported_ = false; |
311 | | bool TestEnv::fallocate_punch_hole_supported_ = false; |
312 | | |
313 | 2 | TEST_P(TestEnv, TestPreallocate) { |
314 | 2 | if (!fallocate_supported_) { |
315 | 2 | LOG(INFO) << "fallocate not supported, skipping test"; |
316 | 2 | return; |
317 | 2 | } |
318 | 0 | LOG(INFO) << "Testing PreAllocate()"; |
319 | 0 | string test_path = GetTestPath("test_env_wf"); |
320 | 0 | shared_ptr<WritableFile> file; |
321 | 0 | WritableFileOptions opts; |
322 | 0 | opts.o_direct = GetParam(); |
323 | 0 | ASSERT_OK(env_util::OpenFileForWrite(opts, |
324 | 0 | env_.get(), test_path, &file)); |
325 | | |
326 | | // pre-allocate 1 MB |
327 | 0 | ASSERT_OK(file->PreAllocate(kOneMb)); |
328 | 0 | ASSERT_OK(file->Sync()); |
329 | | |
330 | | // the writable file size should report 0 |
331 | 0 | ASSERT_EQ(file->Size(), 0); |
332 | | // but the real size of the file on disk should report 1MB |
333 | 0 | uint64_t size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
334 | 0 | ASSERT_EQ(size, kOneMb); |
335 | | |
336 | | // write 1 MB |
337 | 0 | uint8_t scratch[kOneMb]; |
338 | 0 | Slice slice(scratch, kOneMb); |
339 | 0 | ASSERT_OK(file->Append(slice)); |
340 | 0 | ASSERT_OK(file->Sync()); |
341 | | |
342 | | // the writable file size should now report 1 MB |
343 | 0 | ASSERT_EQ(file->Size(), kOneMb); |
344 | 0 | ASSERT_OK(file->Close()); |
345 | | // and the real size for the file on disk should match ony the |
346 | | // written size |
347 | 0 | size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
348 | 0 | ASSERT_EQ(kOneMb, size); |
349 | 0 | } |
350 | | |
351 | | // To test consecutive pre-allocations we need higher pre-allocations since the |
352 | | // mmapped regions grow in size until 2MBs (so smaller pre-allocations will easily |
353 | | // be smaller than the mmapped regions size). |
354 | 1 | TEST_F(TestEnv, TestConsecutivePreallocate) { |
355 | 1 | if (!fallocate_supported_) { |
356 | 1 | LOG(INFO) << "fallocate not supported, skipping test"; |
357 | 1 | return; |
358 | 1 | } |
359 | 0 | LOG(INFO) << "Testing consecutive PreAllocate()"; |
360 | 0 | string test_path = GetTestPath("test_env_wf"); |
361 | 0 | shared_ptr<WritableFile> file; |
362 | 0 | ASSERT_OK(env_util::OpenFileForWrite( |
363 | 0 | WritableFileOptions(), env_.get(), test_path, &file)); |
364 | | |
365 | | // pre-allocate 64 MB |
366 | 0 | ASSERT_OK(file->PreAllocate(64 * kOneMb)); |
367 | 0 | ASSERT_OK(file->Sync()); |
368 | | |
369 | | // the writable file size should report 0 |
370 | 0 | ASSERT_EQ(file->Size(), 0); |
371 | | // but the real size of the file on disk should report 64 MBs |
372 | 0 | uint64_t size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
373 | 0 | ASSERT_EQ(size, 64 * kOneMb); |
374 | | |
375 | | // write 1 MB |
376 | 0 | uint8_t scratch[kOneMb]; |
377 | 0 | Slice slice(scratch, kOneMb); |
378 | 0 | ASSERT_OK(file->Append(slice)); |
379 | 0 | ASSERT_OK(file->Sync()); |
380 | | |
381 | | // the writable file size should now report 1 MB |
382 | 0 | ASSERT_EQ(kOneMb, file->Size()); |
383 | 0 | size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
384 | 0 | ASSERT_EQ(64 * kOneMb, size); |
385 | | |
386 | | // pre-allocate 64 additional MBs |
387 | 0 | ASSERT_OK(file->PreAllocate(64 * kOneMb)); |
388 | 0 | ASSERT_OK(file->Sync()); |
389 | | |
390 | | // the writable file size should now report 1 MB |
391 | 0 | ASSERT_EQ(kOneMb, file->Size()); |
392 | | // while the real file size should report 128 MB's |
393 | 0 | size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
394 | 0 | ASSERT_EQ(128 * kOneMb, size); |
395 | | |
396 | | // write another MB |
397 | 0 | ASSERT_OK(file->Append(slice)); |
398 | 0 | ASSERT_OK(file->Sync()); |
399 | | |
400 | | // the writable file size should now report 2 MB |
401 | 0 | ASSERT_EQ(file->Size(), 2 * kOneMb); |
402 | | // while the real file size should reamin at 128 MBs |
403 | 0 | size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
404 | 0 | ASSERT_EQ(128 * kOneMb, size); |
405 | | |
406 | | // close the file (which ftruncates it to the real size) |
407 | 0 | ASSERT_OK(file->Close()); |
408 | | // and the real size for the file on disk should match only the written size |
409 | 0 | size = ASSERT_RESULT(env_->GetFileSize(test_path)); |
410 | 0 | ASSERT_EQ(2* kOneMb, size); |
411 | |
|
412 | 0 | } |
413 | | |
414 | 1 | TEST_F(TestEnv, TestHolePunch) { |
415 | 1 | if (!fallocate_punch_hole_supported_) { |
416 | 1 | LOG(INFO) << "hole punching not supported, skipping test"; |
417 | 1 | return; |
418 | 1 | } |
419 | 0 | string test_path = GetTestPath("test_env_wf"); |
420 | 0 | std::unique_ptr<RWFile> file; |
421 | 0 | ASSERT_OK(env_->NewRWFile(test_path, &file)); |
422 | | |
423 | | // Write 1 MB. The size and size-on-disk both agree. |
424 | 0 | uint8_t scratch[kOneMb]; |
425 | 0 | Slice slice(scratch, kOneMb); |
426 | 0 | ASSERT_OK(file->Write(0, slice)); |
427 | 0 | ASSERT_OK(file->Sync()); |
428 | 0 | uint64_t sz; |
429 | 0 | ASSERT_OK(file->Size(&sz)); |
430 | 0 | ASSERT_EQ(kOneMb, sz); |
431 | 0 | uint64_t size_on_disk = ASSERT_RESULT(env_->GetFileSizeOnDisk(test_path)); |
432 | | // Some kernels and filesystems (e.g. Centos 6.6 with XFS) aggressively |
433 | | // preallocate file disk space when writing to files, so the disk space may be |
434 | | // greater than 1MiB. |
435 | 0 | ASSERT_LE(kOneMb, size_on_disk); |
436 | | |
437 | | // Punch some data out at byte marker 4096. Now the two sizes diverge. |
438 | 0 | uint64_t punch_amount = 4096 * 4; |
439 | 0 | uint64_t new_size_on_disk; |
440 | 0 | ASSERT_OK(file->PunchHole(4096, punch_amount)); |
441 | 0 | ASSERT_OK(file->Size(&sz)); |
442 | 0 | ASSERT_EQ(kOneMb, sz); |
443 | 0 | new_size_on_disk = ASSERT_RESULT(env_->GetFileSizeOnDisk(test_path)); |
444 | 0 | ASSERT_EQ(size_on_disk - punch_amount, new_size_on_disk); |
445 | 0 | } |
446 | | |
447 | | class ShortReadRandomAccessFile : public RandomAccessFile { |
448 | | public: |
449 | | explicit ShortReadRandomAccessFile(shared_ptr<RandomAccessFile> wrapped) |
450 | 1 | : wrapped_(std::move(wrapped)), seed_(SeedRandom()) {} |
451 | | |
452 | | virtual Status Read(uint64_t offset, size_t n, Slice* result, |
453 | 8 | uint8_t *scratch) const override { |
454 | 8 | CHECK_GT(n, 0); |
455 | | // Divide the requested amount of data by a small integer, |
456 | | // and issue the shorter read to the underlying file. |
457 | 8 | auto short_n = n / ((rand_r(&seed_) % 3) + 1); |
458 | 8 | if (short_n == 0) { |
459 | 0 | short_n = 1; |
460 | 0 | } |
461 | | |
462 | 0 | VLOG(1) << "Reading " << short_n << " instead of " << n; |
463 | | |
464 | 8 | return wrapped_->Read(offset, short_n, result, scratch); |
465 | 8 | } |
466 | | |
467 | 0 | Result<uint64_t> Size() const override { |
468 | 0 | return wrapped_->Size(); |
469 | 0 | } |
470 | | |
471 | 0 | Result<uint64_t> INode() const override { |
472 | 0 | return wrapped_->INode(); |
473 | 0 | } |
474 | | |
475 | 0 | const string& filename() const override { return wrapped_->filename(); } |
476 | | |
477 | 0 | size_t memory_footprint() const override { |
478 | 0 | return wrapped_->memory_footprint(); |
479 | 0 | } |
480 | | |
481 | | private: |
482 | | const shared_ptr<RandomAccessFile> wrapped_; |
483 | | mutable unsigned int seed_; |
484 | | }; |
485 | | |
486 | | // Write 'size' bytes of data to a file, with a simple pattern stored in it. |
487 | 3 | static void WriteTestFile(Env* env, const string& path, size_t size) { |
488 | 3 | shared_ptr<WritableFile> wf; |
489 | 3 | ASSERT_OK(env_util::OpenFileForWrite(env, path, &wf)); |
490 | 3 | faststring data; |
491 | 3 | data.resize(size); |
492 | 1.11M | for (size_t i = 0; i < data.size(); i++) { |
493 | 1.11M | data[i] = (i * 31) & 0xff; |
494 | 1.11M | } |
495 | 3 | ASSERT_OK(wf->Append(Slice(data))); |
496 | 3 | ASSERT_OK(wf->Close()); |
497 | 3 | } |
498 | | |
499 | 1 | TEST_F(TestEnv, TestReadFully) { |
500 | 1 | SeedRandom(); |
501 | 1 | const string kTestPath = "test"; |
502 | 1 | const int kFileSize = 64 * 1024; |
503 | 1 | std::unique_ptr<Env> mem(NewMemEnv(Env::Default())); |
504 | | |
505 | 1 | WriteTestFile(mem.get(), kTestPath, kFileSize); |
506 | 1 | ASSERT_NO_FATALS(); |
507 | | |
508 | | // Reopen for read |
509 | 1 | shared_ptr<RandomAccessFile> raf; |
510 | 1 | ASSERT_OK(env_util::OpenFileForRandom(mem.get(), kTestPath, &raf)); |
511 | | |
512 | 1 | ShortReadRandomAccessFile sr_raf(raf); |
513 | | |
514 | 1 | const int kReadLength = 10000; |
515 | 1 | Slice s; |
516 | 1 | std::unique_ptr<uint8_t[]> scratch(new uint8_t[kReadLength]); |
517 | | |
518 | | // Verify that ReadFully reads the whole requested data. |
519 | 1 | ASSERT_OK(env_util::ReadFully(&sr_raf, 0, kReadLength, &s, scratch.get())); |
520 | 2 | ASSERT_EQ(s.data(), scratch.get()) << "Should have returned a contiguous copy"; |
521 | 1 | ASSERT_EQ(kReadLength, s.size()); |
522 | | |
523 | | // Verify that the data read was correct. |
524 | 1 | VerifyTestData(s, 0); |
525 | | |
526 | | // Verify that ReadFully fails with an IOError at EOF. |
527 | 1 | Status status = env_util::ReadFully(&sr_raf, kFileSize - 100, 200, &s, scratch.get()); |
528 | 1 | ASSERT_FALSE(status.ok()); |
529 | 1 | ASSERT_TRUE(status.IsIOError()); |
530 | 1 | ASSERT_STR_CONTAINS(status.ToString(), "EOF"); |
531 | 1 | } |
532 | | |
533 | 2 | TEST_P(TestEnv, TestAppendVector) { |
534 | 2 | WritableFileOptions opts; |
535 | 2 | opts.o_direct = GetParam(); |
536 | 2 | LOG(INFO) << "Testing AppendVector() only, NO pre-allocation"; |
537 | 2 | ASSERT_NO_FATALS(TestAppendVector(2000, 1024, 5, true, false, opts)); |
538 | | |
539 | 2 | if (!fallocate_supported_) { |
540 | 2 | LOG(INFO) << "fallocate not supported, skipping preallocated runs"; |
541 | 0 | } else { |
542 | 0 | LOG(INFO) << "Testing AppendVector() only, WITH pre-allocation"; |
543 | 0 | ASSERT_NO_FATALS(TestAppendVector(2000, 1024, 5, true, true, opts)); |
544 | 0 | LOG(INFO) << "Testing AppendVector() together with Append() and Read(), WITH pre-allocation"; |
545 | 0 | ASSERT_NO_FATALS(TestAppendVector(128, 4096, 5, false, true, opts)); |
546 | 0 | } |
547 | 2 | } |
548 | | |
549 | 1 | TEST_F(TestEnv, TestRandomData) { |
550 | 1 | WritableFileOptions opts; |
551 | 1 | opts.o_direct = true; |
552 | 1 | LOG(INFO) << "Testing Append() with random data and requests of random sizes"; |
553 | 1 | ASSERT_NO_FATALS(TestAppendRandomData(true, opts)); |
554 | 1 | } |
555 | | |
556 | 1 | TEST_F(TestEnv, TestGetExecutablePath) { |
557 | 1 | string p; |
558 | 1 | ASSERT_OK(Env::Default()->GetExecutablePath(&p)); |
559 | 2 | ASSERT_TRUE(HasSuffixString(p, "env-test")) << p; |
560 | 1 | } |
561 | | |
562 | 1 | TEST_F(TestEnv, TestOpenEmptyRandomAccessFile) { |
563 | 1 | Env* env = Env::Default(); |
564 | 1 | string test_file = JoinPathSegments(GetTestDataDirectory(), "test_file"); |
565 | 1 | ASSERT_NO_FATALS(WriteTestFile(env, test_file, 0)); |
566 | 1 | std::unique_ptr<RandomAccessFile> readable_file; |
567 | 1 | ASSERT_OK(env->NewRandomAccessFile(test_file, &readable_file)); |
568 | 1 | uint64_t size = ASSERT_RESULT(readable_file->Size()); |
569 | 1 | ASSERT_EQ(0, size); |
570 | 1 | } |
571 | | |
572 | 1 | TEST_F(TestEnv, TestOverwrite) { |
573 | 1 | string test_path = GetTestPath("test_env_wf"); |
574 | | |
575 | | // File does not exist, create it. |
576 | 1 | shared_ptr<WritableFile> writer; |
577 | 1 | ASSERT_OK(env_util::OpenFileForWrite(env_.get(), test_path, &writer)); |
578 | | |
579 | | // File exists, overwrite it. |
580 | 1 | ASSERT_OK(env_util::OpenFileForWrite(env_.get(), test_path, &writer)); |
581 | | |
582 | | // File exists, try to overwrite (and fail). |
583 | 1 | WritableFileOptions opts; |
584 | 1 | opts.mode = Env::CREATE_NON_EXISTING; |
585 | 1 | Status s = env_util::OpenFileForWrite(opts, |
586 | 1 | env_.get(), test_path, &writer); |
587 | 1 | ASSERT_TRUE(s.IsAlreadyPresent()); |
588 | 1 | } |
589 | | |
590 | 1 | TEST_F(TestEnv, TestReopen) { |
591 | 1 | LOG(INFO) << "Testing reopening behavior"; |
592 | 1 | string test_path = GetTestPath("test_env_wf"); |
593 | 1 | string first = "The quick brown fox"; |
594 | 1 | string second = "jumps over the lazy dog"; |
595 | | |
596 | | // Create the file and write to it. |
597 | 1 | shared_ptr<WritableFile> writer; |
598 | 1 | ASSERT_OK(env_util::OpenFileForWrite(WritableFileOptions(), |
599 | 1 | env_.get(), test_path, &writer)); |
600 | 1 | ASSERT_OK(writer->Append(first)); |
601 | 1 | ASSERT_EQ(first.length(), writer->Size()); |
602 | 1 | ASSERT_OK(writer->Close()); |
603 | | |
604 | | // Reopen it and append to it. |
605 | 1 | WritableFileOptions reopen_opts; |
606 | 1 | reopen_opts.mode = Env::OPEN_EXISTING; |
607 | 1 | ASSERT_OK(env_util::OpenFileForWrite(reopen_opts, |
608 | 1 | env_.get(), test_path, &writer)); |
609 | 1 | ASSERT_EQ(first.length(), writer->Size()); |
610 | 1 | ASSERT_OK(writer->Append(second)); |
611 | 1 | ASSERT_EQ(first.length() + second.length(), writer->Size()); |
612 | 1 | ASSERT_OK(writer->Close()); |
613 | | |
614 | | // Check that the file has both strings. |
615 | 1 | shared_ptr<RandomAccessFile> reader; |
616 | 1 | ASSERT_OK(env_util::OpenFileForRandom(env_.get(), test_path, &reader)); |
617 | 1 | uint64_t size = ASSERT_RESULT(reader->Size()); |
618 | 1 | ASSERT_EQ(first.length() + second.length(), size); |
619 | 1 | Slice s; |
620 | 1 | std::vector<uint8_t> scratch(size); |
621 | 1 | ASSERT_OK(env_util::ReadFully(reader.get(), 0, size, &s, scratch.data())); |
622 | 1 | ASSERT_EQ(first + second, s.ToString()); |
623 | 1 | } |
624 | | |
625 | 1 | TEST_F(TestEnv, TestIsDirectory) { |
626 | 1 | string dir = GetTestPath("a_directory"); |
627 | 1 | ASSERT_OK(env_->CreateDir(dir)); |
628 | 1 | bool is_dir; |
629 | 1 | ASSERT_OK(env_->IsDirectory(dir, &is_dir)); |
630 | 1 | ASSERT_TRUE(is_dir); |
631 | | |
632 | 1 | string not_dir = GetTestPath("not_a_directory"); |
633 | 1 | std::unique_ptr<WritableFile> writer; |
634 | 1 | ASSERT_OK(env_->NewWritableFile(not_dir, &writer)); |
635 | 1 | ASSERT_OK(env_->IsDirectory(not_dir, &is_dir)); |
636 | 1 | ASSERT_FALSE(is_dir); |
637 | 1 | } |
638 | | |
639 | | static Status TestWalkCb(vector<string>* actual, |
640 | | Env::FileType type, |
641 | 12 | const string& dirname, const string& basename) { |
642 | 0 | VLOG(1) << type << ":" << dirname << ":" << basename; |
643 | 12 | actual->push_back(JoinPathSegments(dirname, basename)); |
644 | 12 | return Status::OK(); |
645 | 12 | } |
646 | | |
647 | 4 | static Status CreateDir(Env* env, const string& name, vector<string>* created) { |
648 | 4 | RETURN_NOT_OK(env->CreateDir(name)); |
649 | 4 | created->push_back(name); |
650 | 4 | return Status::OK(); |
651 | 4 | } |
652 | | |
653 | 8 | static Status CreateFile(Env* env, const string& name, vector<string>* created) { |
654 | 8 | std::unique_ptr<WritableFile> writer; |
655 | 8 | RETURN_NOT_OK(env->NewWritableFile(name, &writer)); |
656 | 8 | created->push_back(writer->filename()); |
657 | 8 | return Status::OK(); |
658 | 8 | } |
659 | | |
660 | 1 | TEST_F(TestEnv, TestWalk) { |
661 | | // We test with this tree: |
662 | | // |
663 | | // /root/ |
664 | | // /root/file_1 |
665 | | // /root/file_2 |
666 | | // /root/dir_a/file_1 |
667 | | // /root/dir_a/file_2 |
668 | | // /root/dir_b/file_1 |
669 | | // /root/dir_b/file_2 |
670 | | // /root/dir_b/dir_c/file_1 |
671 | | // /root/dir_b/dir_c/file_2 |
672 | 1 | string root = GetTestPath("root"); |
673 | 1 | string subdir_a = JoinPathSegments(root, "dir_a"); |
674 | 1 | string subdir_b = JoinPathSegments(root, "dir_b"); |
675 | 1 | string subdir_c = JoinPathSegments(subdir_b, "dir_c"); |
676 | 1 | string file_one = "file_1"; |
677 | 1 | string file_two = "file_2"; |
678 | 1 | vector<string> expected; |
679 | 1 | ASSERT_OK(CreateDir(env_.get(), root, &expected)); |
680 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(root, file_one), &expected)); |
681 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(root, file_two), &expected)); |
682 | 1 | ASSERT_OK(CreateDir(env_.get(), subdir_a, &expected)); |
683 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_a, file_one), &expected)); |
684 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_a, file_two), &expected)); |
685 | 1 | ASSERT_OK(CreateDir(env_.get(), subdir_b, &expected)); |
686 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_b, file_one), &expected)); |
687 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_b, file_two), &expected)); |
688 | 1 | ASSERT_OK(CreateDir(env_.get(), subdir_c, &expected)); |
689 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_c, file_one), &expected)); |
690 | 1 | ASSERT_OK(CreateFile(env_.get(), JoinPathSegments(subdir_c, file_two), &expected)); |
691 | | |
692 | | // Do the walk. |
693 | | // |
694 | | // Sadly, tr1/unordered_set doesn't implement equality operators, so we |
695 | | // compare sorted vectors instead. |
696 | 1 | vector<string> actual; |
697 | 1 | ASSERT_OK(env_->Walk(root, Env::PRE_ORDER, std::bind(&TestWalkCb, &actual, _1, _2, _3))); |
698 | 1 | sort(expected.begin(), expected.end()); |
699 | 1 | sort(actual.begin(), actual.end()); |
700 | 1 | ASSERT_EQ(expected, actual); |
701 | 1 | } |
702 | | |
703 | | static Status TestWalkErrorCb(int* num_calls, |
704 | | Env::FileType type, |
705 | 2 | const string& dirname, const string& basename) { |
706 | 2 | (*num_calls)++; |
707 | 2 | return STATUS(Aborted, "Returning abort status"); |
708 | 2 | } |
709 | | |
710 | 1 | TEST_F(TestEnv, TestWalkCbReturnsError) { |
711 | 1 | string new_dir = GetTestPath("foo"); |
712 | 1 | string new_file = "myfile"; |
713 | 1 | ASSERT_OK(env_->CreateDir(new_dir)); |
714 | 1 | std::unique_ptr<WritableFile> writer; |
715 | 1 | ASSERT_OK(env_->NewWritableFile(JoinPathSegments(new_dir, new_file), &writer)); |
716 | 1 | int num_calls = 0; |
717 | 1 | ASSERT_TRUE(env_->Walk(new_dir, Env::PRE_ORDER, |
718 | 1 | std::bind(&TestWalkErrorCb, &num_calls, _1, _2, _3)).IsIOError()); |
719 | | |
720 | | // Once for the directory and once for the file inside it. |
721 | 1 | ASSERT_EQ(2, num_calls); |
722 | 1 | } |
723 | | |
724 | 1 | TEST_F(TestEnv, TestGetBlockSize) { |
725 | | // Does not exist. |
726 | 1 | auto result = env_->GetBlockSize("does_not_exist"); |
727 | 1 | ASSERT_TRUE(!result.ok() && result.status().IsNotFound()); |
728 | | |
729 | | // Try with a directory. |
730 | 1 | auto block_size = ASSERT_RESULT(env_->GetBlockSize(".")); |
731 | 1 | ASSERT_GT(block_size, 0); |
732 | | |
733 | | // Try with a file. |
734 | 1 | string path = GetTestPath("foo"); |
735 | 1 | std::unique_ptr<WritableFile> writer; |
736 | 1 | ASSERT_OK(env_->NewWritableFile(path, &writer)); |
737 | 1 | block_size = ASSERT_RESULT(env_->GetBlockSize(path)); |
738 | 1 | ASSERT_GT(block_size, 0); |
739 | 1 | } |
740 | | |
741 | 1 | TEST_F(TestEnv, TestRWFile) { |
742 | | // Create the file. |
743 | 1 | std::unique_ptr<RWFile> file; |
744 | 1 | ASSERT_OK(env_->NewRWFile(GetTestPath("foo"), &file)); |
745 | | |
746 | | // Append to it. |
747 | 1 | string kTestData = "abcde"; |
748 | 1 | ASSERT_OK(file->Write(0, kTestData)); |
749 | | |
750 | | // Read from it. |
751 | 1 | Slice result; |
752 | 1 | std::unique_ptr<uint8_t[]> scratch(new uint8_t[kTestData.length()]); |
753 | 1 | ASSERT_OK(file->Read(0, kTestData.length(), &result, scratch.get())); |
754 | 1 | ASSERT_EQ(result, kTestData); |
755 | 1 | uint64_t sz; |
756 | 1 | ASSERT_OK(file->Size(&sz)); |
757 | 1 | ASSERT_EQ(kTestData.length(), sz); |
758 | | |
759 | | // Write past the end of the file and rewrite some of the interior. |
760 | 1 | ASSERT_OK(file->Write(kTestData.length() * 2, kTestData)); |
761 | 1 | ASSERT_OK(file->Write(kTestData.length(), kTestData)); |
762 | 1 | ASSERT_OK(file->Write(1, kTestData)); |
763 | 1 | string kNewTestData = "aabcdebcdeabcde"; |
764 | 1 | std::unique_ptr<uint8_t[]> scratch2(new uint8_t[kNewTestData.length()]); |
765 | 1 | ASSERT_OK(file->Read(0, kNewTestData.length(), &result, scratch2.get())); |
766 | | |
767 | | // Retest. |
768 | 1 | ASSERT_EQ(result, kNewTestData); |
769 | 1 | ASSERT_OK(file->Size(&sz)); |
770 | 1 | ASSERT_EQ(kNewTestData.length(), sz); |
771 | | |
772 | | // Make sure we can't overwrite it. |
773 | 1 | RWFileOptions opts; |
774 | 1 | opts.mode = Env::CREATE_NON_EXISTING; |
775 | 1 | ASSERT_TRUE(env_->NewRWFile(opts, GetTestPath("foo"), &file).IsAlreadyPresent()); |
776 | | |
777 | | // Reopen it without truncating the existing data. |
778 | 1 | opts.mode = Env::OPEN_EXISTING; |
779 | 1 | ASSERT_OK(env_->NewRWFile(opts, GetTestPath("foo"), &file)); |
780 | 1 | ASSERT_OK(file->Read(0, kNewTestData.length(), &result, scratch2.get())); |
781 | 1 | ASSERT_EQ(result, kNewTestData); |
782 | 1 | } |
783 | | |
784 | 1 | TEST_F(TestEnv, TestCanonicalize) { |
785 | 1 | vector<string> synonyms = { GetTestPath("."), GetTestPath("./."), GetTestPath(".//./") }; |
786 | 3 | for (const string& synonym : synonyms) { |
787 | 3 | string result; |
788 | 3 | ASSERT_OK(env_->Canonicalize(synonym, &result)); |
789 | 3 | ASSERT_EQ(GetTestDataDirectory(), result); |
790 | 3 | } |
791 | | |
792 | 1 | string dir = GetTestPath("some_dir"); |
793 | 1 | ASSERT_OK(env_->CreateDir(dir)); |
794 | 1 | string result; |
795 | 1 | ASSERT_OK(env_->Canonicalize(dir + "/", &result)); |
796 | 1 | ASSERT_EQ(dir, result); |
797 | | |
798 | 1 | ASSERT_TRUE(env_->Canonicalize(dir + "/bar", nullptr).IsNotFound()); |
799 | 1 | } |
800 | | |
801 | 1 | TEST_F(TestEnv, TestGetTotalRAMBytes) { |
802 | 1 | int64_t ram = 0; |
803 | 1 | ASSERT_OK(env_->GetTotalRAMBytes(&ram)); |
804 | | |
805 | | // Can't test much about it. |
806 | 1 | ASSERT_GT(ram, 0); |
807 | 1 | } |
808 | | |
809 | 1 | TEST_F(TestEnv, TestGetFreeSpace) { |
810 | 1 | char cwd[1024]; |
811 | 1 | char* ret = getcwd(cwd, sizeof(cwd)); |
812 | 1 | ASSERT_NE(ret, nullptr); |
813 | | |
814 | 1 | constexpr int64_t kMaxAllowedDeltaBytes = 65536; |
815 | | |
816 | | // Number of times the difference between the return value from GetFreeSpaceBytes and |
817 | | // the output from command 'df' should be less than kMaxAllowedDeltaBytes before we consider |
818 | | // this test has passed. |
819 | 1 | constexpr int kCountRequired = 10; |
820 | | |
821 | | // Minimum block size for MacOS is 512. |
822 | 1 | constexpr int block_size = 512; |
823 | 1 | const string cmd = strings::Substitute( |
824 | 1 | "(export BLOCKSIZE=$0; df $1 | tail -1 | awk '{print $$4}' | tr -d '\\n')", block_size, cwd); |
825 | | |
826 | 1 | int success_count = 0; |
827 | 10 | for (int i = 0; i < kCountRequired * 10; i++) { |
828 | 10 | const int64_t free_space = static_cast<int64_t>(ASSERT_RESULT(env_->GetFreeSpaceBytes(cwd))); |
829 | | |
830 | 10 | string df_free_space_str; |
831 | 10 | ASSERT_TRUE(RunShellProcess(cmd, &df_free_space_str)); |
832 | 10 | const int64_t df_free_space = block_size * ASSERT_RESULT(CheckedStoll(df_free_space_str)); |
833 | | |
834 | | // We might not get the exact same answer because disk space is being consumed and freed. |
835 | 10 | const int64_t delta_bytes = abs(df_free_space - free_space); |
836 | 10 | if (delta_bytes > kMaxAllowedDeltaBytes) { |
837 | 0 | LOG(INFO) << "df returned: " << df_free_space |
838 | 0 | << ", GetFreeSpaceBytes returned: " << free_space; |
839 | 10 | } else { |
840 | 10 | success_count++; |
841 | 10 | if (success_count >= kCountRequired) { |
842 | 1 | break; |
843 | 1 | } |
844 | 10 | } |
845 | 10 | } |
846 | 1 | ASSERT_GE(success_count, kCountRequired); |
847 | 1 | } |
848 | | |
849 | | // Test that CopyFile() copies all the bytes properly. |
850 | 1 | TEST_F(TestEnv, TestCopyFile) { |
851 | 1 | string orig_path = GetTestPath("test"); |
852 | 1 | string copy_path = orig_path + ".copy"; |
853 | 1 | const int kFileSize = 1024 * 1024 + 11; // Some odd number of bytes. |
854 | | |
855 | 1 | Env* env = Env::Default(); |
856 | 1 | ASSERT_NO_FATALS(WriteTestFile(env, orig_path, kFileSize)); |
857 | 1 | ASSERT_OK(env_util::CopyFile(env, orig_path, copy_path, WritableFileOptions())); |
858 | 1 | std::unique_ptr<RandomAccessFile> copy; |
859 | 1 | ASSERT_OK(env->NewRandomAccessFile(copy_path, ©)); |
860 | 1 | ASSERT_NO_FATALS(ReadAndVerifyTestData(copy.get(), 0, kFileSize)); |
861 | 1 | } |
862 | | |
863 | | INSTANTIATE_TEST_CASE_P(BufferedIO, TestEnv, ::testing::Values(false)); |
864 | | INSTANTIATE_TEST_CASE_P(DirectIO, TestEnv, ::testing::Values(true)); |
865 | | |
866 | | } // namespace yb |