1#include "catch.hpp"
2#include "duckdb/common/file_buffer.hpp"
3#include "duckdb/common/file_system.hpp"
4#include "duckdb/common/fstream.hpp"
5#include "test_helpers.hpp"
6
7using namespace duckdb;
8using namespace std;
9
10static void create_dummy_file(string fname) {
11 ofstream outfile(fname);
12 outfile << "I_AM_A_DUMMY" << endl;
13 outfile.close();
14}
15
16TEST_CASE("Make sure file system operators work as advertised", "[file_system]") {
17 FileSystem fs;
18 auto dname = TestCreatePath("TEST_DIR");
19 string fname = "TEST_FILE";
20 string fname2 = "TEST_FILE_TWO";
21
22 if (fs.DirectoryExists(dname)) {
23 fs.RemoveDirectory(dname);
24 }
25
26 fs.CreateDirectory(dname);
27 REQUIRE(fs.DirectoryExists(dname));
28 REQUIRE(!fs.FileExists(dname));
29
30 // we can call this again and nothing happens
31 fs.CreateDirectory(dname);
32
33 auto fname_in_dir = fs.JoinPath(dname, fname);
34 auto fname_in_dir2 = fs.JoinPath(dname, fname2);
35
36 create_dummy_file(fname_in_dir);
37 REQUIRE(fs.FileExists(fname_in_dir));
38 REQUIRE(!fs.DirectoryExists(fname_in_dir));
39
40 size_t n_files = 0;
41 REQUIRE(fs.ListFiles(dname, [&n_files](const string &path) { n_files++; }));
42
43 REQUIRE(n_files == 1);
44
45 REQUIRE(fs.FileExists(fname_in_dir));
46 REQUIRE(!fs.FileExists(fname_in_dir2));
47
48 fs.MoveFile(fname_in_dir, fname_in_dir2);
49
50 REQUIRE(!fs.FileExists(fname_in_dir));
51 REQUIRE(fs.FileExists(fname_in_dir2));
52
53 fs.RemoveDirectory(dname);
54
55 REQUIRE(!fs.DirectoryExists(dname));
56 REQUIRE(!fs.FileExists(fname_in_dir));
57 REQUIRE(!fs.FileExists(fname_in_dir2));
58}
59
60// note: the integer count is chosen as 512 so that we write 512*8=4096 bytes to the file
61// this is required for the Direct-IO as on Windows Direct-IO can only write multiples of sector sizes
62// sector sizes are typically one of [512/1024/2048/4096] bytes, hence a 4096 bytes write succeeds.
63#define INTEGER_COUNT 512
64
65TEST_CASE("Test file operations", "[file_system]") {
66 FileSystem fs;
67 unique_ptr<FileHandle> handle, handle2;
68 int64_t test_data[INTEGER_COUNT];
69 for (int i = 0; i < INTEGER_COUNT; i++) {
70 test_data[i] = i;
71 }
72
73 auto fname = TestCreatePath("test_file");
74
75 // standard reading/writing test
76
77 // open file for writing
78 REQUIRE_NOTHROW(handle = fs.OpenFile(fname, FileFlags::WRITE | FileFlags::CREATE, FileLockType::NO_LOCK));
79 // write 10 integers
80 REQUIRE_NOTHROW(handle->Write((void *)test_data, sizeof(int64_t) * INTEGER_COUNT, 0));
81 // close the file
82 handle.reset();
83
84 for (int i = 0; i < INTEGER_COUNT; i++) {
85 test_data[i] = 0;
86 }
87 // now open the file for reading
88 REQUIRE_NOTHROW(handle = fs.OpenFile(fname, FileFlags::READ, FileLockType::NO_LOCK));
89 // read the 10 integers back
90 REQUIRE_NOTHROW(handle->Read((void *)test_data, sizeof(int64_t) * INTEGER_COUNT, 0));
91 // check the values of the integers
92 for (int i = 0; i < 10; i++) {
93 REQUIRE(test_data[i] == i);
94 }
95 handle.reset();
96 fs.RemoveFile(fname);
97}
98
99TEST_CASE("Test file buffers for reading/writing to file", "[file_system]") {
100 FileSystem fs;
101 unique_ptr<FileHandle> handle;
102
103 auto fname = TestCreatePath("test_file");
104
105 // create the buffer and fill it with data
106 auto buf = make_unique<FileBuffer>(FileBufferType::MANAGED_BUFFER, 4096);
107 int64_t *ptr = (int64_t *)buf->buffer;
108 for (size_t i = 0; i < 10; i++) {
109 ptr[i] = i;
110 }
111
112 // open file for writing
113 REQUIRE_NOTHROW(handle = fs.OpenFile(fname, FileFlags::WRITE | FileFlags::CREATE | FileFlags::DIRECT_IO,
114 FileLockType::WRITE_LOCK));
115 // write the buffer
116 REQUIRE_NOTHROW(buf->Write(*handle, 0));
117 // clear the buffer
118 buf->Clear();
119 // now read data back into the buffer
120 REQUIRE_NOTHROW(buf->Read(*handle, 0));
121 for (size_t i = 0; i < 10; i++) {
122 REQUIRE(ptr[i] == i);
123 }
124 // close the file
125 handle.reset();
126
127 fs.RemoveFile(fname);
128}
129