1 /*
2 * Copyright 2023 Google LLC
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "fcp/tensorflow/file_descriptor_filesystem.h"
18
19 #include <fcntl.h>
20
21 #include <memory>
22 #include <vector>
23
24 #include "absl/strings/str_cat.h"
25 #include "absl/strings/string_view.h"
26 #include "android-base/file.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "tensorflow/core/lib/core/status_test_util.h"
30
31 namespace tensorflow {
32 namespace fcp {
33 namespace {
34
35 using ::android::base::ReadFileToString;
36 using ::testing::TempDir;
37
38 static constexpr char kBadFdPath[] = "fd:///10000";
39 static constexpr char kFileContent[] = "abcdefgh";
40 static constexpr int kFileContentLen = 8;
41
42 class FileDescriptorFileSystemTest : public ::testing::Test {
43 protected:
TearDown()44 void TearDown() override {
45 if (fd_ != -1) {
46 ASSERT_NE(-1, close(fd_));
47 }
48 }
49
50 // Writes contents to a temp file and sets fd_path_ to point to it. The opened
51 // file descriptor is closed automatically in TearDown(). To be called at most
52 // once per test.
CreateAndOpenFdForTest(std::string contents)53 void CreateAndOpenFdForTest(std::string contents) {
54 ASSERT_EQ(-1, fd_); // prevent accidental double-open
55 file_name_ = TempDir() + "/fdtest";
56 android::base::WriteStringToFile(contents, file_name_);
57
58 fd_ = open(file_name_.c_str(), O_RDONLY);
59 ASSERT_NE(-1, fd_);
60
61 fd_path_ = absl::StrCat("fd:///", fd_);
62 }
63
CreateTempFdForTest()64 void CreateTempFdForTest() {
65 ASSERT_EQ(-1, fd_); // prevent accidental double-open
66
67 file_name_ = TempDir() + "/fdtest";
68 android::base::WriteStringToFile("", file_name_);
69
70 fd_ = open(file_name_.c_str(), O_WRONLY);
71 ASSERT_NE(-1, fd_);
72
73 fd_path_ = absl::StrCat("fd:///", fd_);
74 }
75
76 FileDescriptorFileSystem fd_fs_;
77 string fd_path_;
78 string file_name_;
79
80 private:
81 int fd_ = -1;
82 };
83
TEST_F(FileDescriptorFileSystemTest,WritableFile)84 TEST_F(FileDescriptorFileSystemTest, WritableFile) {
85 CreateTempFdForTest();
86
87 std::unique_ptr<WritableFile> file;
88 TF_ASSERT_OK(fd_fs_.NewWritableFile(fd_path_, &file));
89 TF_ASSERT_OK(file->Append(kFileContent));
90 TF_ASSERT_OK(file->Close());
91
92 std::string actual_content;
93 ASSERT_TRUE(ReadFileToString(file_name_, &actual_content));
94 EXPECT_EQ(kFileContent, actual_content);
95 }
96
TEST_F(FileDescriptorFileSystemTest,WritableFileFailsOnInvalidFd)97 TEST_F(FileDescriptorFileSystemTest, WritableFileFailsOnInvalidFd) {
98 std::unique_ptr<WritableFile> file;
99 EXPECT_FALSE(fd_fs_.NewWritableFile(kBadFdPath, &file).ok());
100 }
101
TEST_F(FileDescriptorFileSystemTest,MalformedPathReturnsInvalidArgument)102 TEST_F(FileDescriptorFileSystemTest, MalformedPathReturnsInvalidArgument) {
103 FileStatistics stats;
104 EXPECT_EQ(fd_fs_.Stat("fd://0", &stats).code(), error::INVALID_ARGUMENT);
105 EXPECT_EQ(fd_fs_.Stat("fd://authority/0", &stats).code(),
106 error::INVALID_ARGUMENT);
107 EXPECT_EQ(fd_fs_.Stat("fd:///not-a-number", &stats).code(),
108 error::INVALID_ARGUMENT);
109 }
110
TEST_F(FileDescriptorFileSystemTest,NewRandomAccessFile)111 TEST_F(FileDescriptorFileSystemTest, NewRandomAccessFile) {
112 CreateAndOpenFdForTest(kFileContent);
113
114 std::unique_ptr<RandomAccessFile> file;
115 TF_ASSERT_OK(fd_fs_.NewRandomAccessFile(fd_path_, &file));
116 StringPiece content;
117 char scratch[kFileContentLen];
118 TF_ASSERT_OK(file->Read(0, kFileContentLen, &content, scratch));
119
120 EXPECT_EQ(kFileContent, content);
121 }
122
TEST_F(FileDescriptorFileSystemTest,NewRandomAccessFileFailsOnRequestMoreBytes)123 TEST_F(FileDescriptorFileSystemTest,
124 NewRandomAccessFileFailsOnRequestMoreBytes) {
125 CreateAndOpenFdForTest(kFileContent);
126
127 std::unique_ptr<RandomAccessFile> file;
128 TF_ASSERT_OK(fd_fs_.NewRandomAccessFile(fd_path_, &file));
129 StringPiece content;
130 char scratch[kFileContentLen];
131 auto status = file->Read(0, kFileContentLen + 2, &content, scratch);
132 EXPECT_EQ(status.code(), error::OUT_OF_RANGE);
133 EXPECT_EQ(status.error_message(),
134 "Read fewer bytes than requested. Total read bytes 8");
135 }
136
TEST_F(FileDescriptorFileSystemTest,NewRandomAccessFileFailsOnInvalidFd)137 TEST_F(FileDescriptorFileSystemTest, NewRandomAccessFileFailsOnInvalidFd) {
138 std::unique_ptr<RandomAccessFile> file;
139 EXPECT_FALSE(fd_fs_.NewRandomAccessFile(kBadFdPath, &file).ok());
140 }
141
TEST_F(FileDescriptorFileSystemTest,NewRandomAccessFileFailsOnDirectoryFd)142 TEST_F(FileDescriptorFileSystemTest, NewRandomAccessFileFailsOnDirectoryFd) {
143 int dir_fd = open(TempDir().c_str(), O_RDONLY | O_DIRECTORY);
144 ASSERT_NE(-1, dir_fd);
145 string dir_fd_path = absl::StrCat("fd:///", dir_fd);
146
147 std::unique_ptr<RandomAccessFile> file;
148 EXPECT_FALSE(fd_fs_.NewRandomAccessFile(dir_fd_path, &file).ok());
149
150 close(dir_fd);
151 }
152
TEST_F(FileDescriptorFileSystemTest,GetMatchingPaths)153 TEST_F(FileDescriptorFileSystemTest, GetMatchingPaths) {
154 CreateAndOpenFdForTest(kFileContent);
155
156 std::vector<string> paths;
157 TF_EXPECT_OK(fd_fs_.GetMatchingPaths(fd_path_, &paths));
158
159 ASSERT_EQ(1, paths.size());
160 EXPECT_EQ(fd_path_, paths.at(0));
161 }
162
TEST_F(FileDescriptorFileSystemTest,GetMatchingPathsReturnsEmptyOnBadFd)163 TEST_F(FileDescriptorFileSystemTest, GetMatchingPathsReturnsEmptyOnBadFd) {
164 std::vector<string> paths;
165 TF_EXPECT_OK(fd_fs_.GetMatchingPaths(kBadFdPath, &paths));
166 EXPECT_TRUE(paths.empty());
167 }
168
TEST_F(FileDescriptorFileSystemTest,Stat)169 TEST_F(FileDescriptorFileSystemTest, Stat) {
170 CreateAndOpenFdForTest(kFileContent);
171
172 FileStatistics stats;
173 TF_EXPECT_OK(fd_fs_.Stat(fd_path_, &stats));
174
175 EXPECT_EQ(kFileContentLen, stats.length);
176 EXPECT_GT(stats.mtime_nsec, 0);
177 EXPECT_FALSE(stats.is_directory);
178 }
179
TEST_F(FileDescriptorFileSystemTest,StatFailsOnBadFd)180 TEST_F(FileDescriptorFileSystemTest, StatFailsOnBadFd) {
181 FileStatistics stats;
182 EXPECT_FALSE(fd_fs_.Stat(kBadFdPath, &stats).ok());
183 }
184
TEST_F(FileDescriptorFileSystemTest,GetFileSize)185 TEST_F(FileDescriptorFileSystemTest, GetFileSize) {
186 CreateAndOpenFdForTest(kFileContent);
187
188 uint64 size;
189 TF_EXPECT_OK(fd_fs_.GetFileSize(fd_path_, &size));
190
191 EXPECT_EQ(kFileContentLen, size);
192 }
193
TEST_F(FileDescriptorFileSystemTest,GetFileSizeFailsOnBadFd)194 TEST_F(FileDescriptorFileSystemTest, GetFileSizeFailsOnBadFd) {
195 uint64 size;
196 EXPECT_FALSE(fd_fs_.GetFileSize(kBadFdPath, &size).ok());
197 }
198
TEST_F(FileDescriptorFileSystemTest,NewReadOnlyMemoryRegionFromFileReturnsUnimplemented)199 TEST_F(FileDescriptorFileSystemTest,
200 NewReadOnlyMemoryRegionFromFileReturnsUnimplemented) {
201 std::unique_ptr<ReadOnlyMemoryRegion> region;
202 EXPECT_EQ(fd_fs_.NewReadOnlyMemoryRegionFromFile(kBadFdPath, ®ion).code(),
203 error::UNIMPLEMENTED);
204 }
205
TEST_F(FileDescriptorFileSystemTest,FileExistsReturnsUnimplemented)206 TEST_F(FileDescriptorFileSystemTest, FileExistsReturnsUnimplemented) {
207 EXPECT_EQ(fd_fs_.FileExists(kBadFdPath).code(), error::UNIMPLEMENTED);
208 }
209
TEST_F(FileDescriptorFileSystemTest,GetChildrenReturnsUnimplemented)210 TEST_F(FileDescriptorFileSystemTest, GetChildrenReturnsUnimplemented) {
211 std::vector<string> paths;
212 EXPECT_EQ(fd_fs_.GetChildren(kBadFdPath, &paths).code(),
213 error::UNIMPLEMENTED);
214 }
215
216 } // anonymous namespace
217 } // namespace fcp
218 } // namespace tensorflow
219