1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow/core/platform/env.h"
17
18 #include <sys/stat.h>
19
20 #include "tensorflow/core/framework/graph.pb.h"
21 #include "tensorflow/core/framework/node_def.pb.h"
22 #include "tensorflow/core/lib/core/status_test_util.h"
23 #include "tensorflow/core/platform/cord.h"
24 #include "tensorflow/core/platform/null_file_system.h"
25 #include "tensorflow/core/platform/path.h"
26 #include "tensorflow/core/platform/protobuf.h"
27 #include "tensorflow/core/platform/str_util.h"
28 #include "tensorflow/core/platform/strcat.h"
29 #include "tensorflow/core/platform/stringpiece.h"
30 #include "tensorflow/core/platform/test.h"
31
32 namespace tensorflow {
33
34 namespace {
35
CreateTestFile(Env * env,const string & filename,int length)36 string CreateTestFile(Env* env, const string& filename, int length) {
37 string input(length, 0);
38 for (int i = 0; i < length; i++) input[i] = i;
39 TF_EXPECT_OK(WriteStringToFile(env, filename, input));
40 return input;
41 }
42
CreateTestProto()43 GraphDef CreateTestProto() {
44 GraphDef g;
45 NodeDef* node = g.add_node();
46 node->set_name("name1");
47 node->set_op("op1");
48 node = g.add_node();
49 node->set_name("name2");
50 node->set_op("op2");
51 return g;
52 }
53
ExpectHasSubstr(StringPiece s,StringPiece expected)54 static void ExpectHasSubstr(StringPiece s, StringPiece expected) {
55 EXPECT_TRUE(absl::StrContains(s, expected))
56 << "'" << s << "' does not contain '" << expected << "'";
57 }
58
59 } // namespace
60
BaseDir()61 string BaseDir() { return io::JoinPath(testing::TmpDir(), "base_dir"); }
62
63 class DefaultEnvTest : public ::testing::Test {
64 protected:
SetUp()65 void SetUp() override { TF_CHECK_OK(env_->CreateDir(BaseDir())); }
66
TearDown()67 void TearDown() override {
68 int64_t undeleted_files, undeleted_dirs;
69 TF_CHECK_OK(
70 env_->DeleteRecursively(BaseDir(), &undeleted_files, &undeleted_dirs));
71 }
72
73 Env* env_ = Env::Default();
74 };
75
TEST_F(DefaultEnvTest,IncompleteReadOutOfRange)76 TEST_F(DefaultEnvTest, IncompleteReadOutOfRange) {
77 const string filename = io::JoinPath(BaseDir(), "out_of_range");
78 const string input = CreateTestFile(env_, filename, 2);
79 std::unique_ptr<RandomAccessFile> f;
80 TF_EXPECT_OK(env_->NewRandomAccessFile(filename, &f));
81
82 // Reading past EOF should give an OUT_OF_RANGE error
83 StringPiece result;
84 char scratch[3];
85 EXPECT_EQ(error::OUT_OF_RANGE, f->Read(0, 3, &result, scratch).code());
86 EXPECT_EQ(input, result);
87
88 // Exact read to EOF works.
89 TF_EXPECT_OK(f->Read(0, 2, &result, scratch));
90 EXPECT_EQ(input, result);
91 }
92
TEST_F(DefaultEnvTest,ReadFileToString)93 TEST_F(DefaultEnvTest, ReadFileToString) {
94 for (const int length : {0, 1, 1212, 2553, 4928, 8196, 9000, (1 << 20) - 1,
95 1 << 20, (1 << 20) + 1, (256 << 20) + 100}) {
96 const string filename =
97 io::JoinPath(BaseDir(), "bar", "..", strings::StrCat("file", length));
98
99 // Write a file with the given length
100 const string input = CreateTestFile(env_, filename, length);
101
102 // Read the file back and check equality
103 string output;
104 TF_EXPECT_OK(ReadFileToString(env_, filename, &output));
105 EXPECT_EQ(length, output.size());
106 EXPECT_EQ(input, output);
107
108 // Obtain stats.
109 FileStatistics stat;
110 TF_EXPECT_OK(env_->Stat(filename, &stat));
111 EXPECT_EQ(length, stat.length);
112 EXPECT_FALSE(stat.is_directory);
113 }
114 }
115
TEST_F(DefaultEnvTest,ReadWriteBinaryProto)116 TEST_F(DefaultEnvTest, ReadWriteBinaryProto) {
117 const GraphDef proto = CreateTestProto();
118 const string filename = strings::StrCat(BaseDir(), "binary_proto");
119
120 // Write the binary proto
121 TF_EXPECT_OK(WriteBinaryProto(env_, filename, proto));
122
123 // Read the binary proto back in and make sure it's the same.
124 GraphDef result;
125 TF_EXPECT_OK(ReadBinaryProto(env_, filename, &result));
126 EXPECT_EQ(result.DebugString(), proto.DebugString());
127
128 // Reading as text or binary proto should also work.
129 GraphDef result2;
130 TF_EXPECT_OK(ReadTextOrBinaryProto(env_, filename, &result2));
131 EXPECT_EQ(result2.DebugString(), proto.DebugString());
132 }
133
TEST_F(DefaultEnvTest,ReadWriteTextProto)134 TEST_F(DefaultEnvTest, ReadWriteTextProto) {
135 const GraphDef proto = CreateTestProto();
136 const string filename = strings::StrCat(BaseDir(), "text_proto");
137
138 // Write the text proto
139 string as_text;
140 EXPECT_TRUE(protobuf::TextFormat::PrintToString(proto, &as_text));
141 TF_EXPECT_OK(WriteStringToFile(env_, filename, as_text));
142
143 // Read the text proto back in and make sure it's the same.
144 GraphDef result;
145 TF_EXPECT_OK(ReadTextProto(env_, filename, &result));
146 EXPECT_EQ(result.DebugString(), proto.DebugString());
147
148 // Reading as text or binary proto should also work.
149 GraphDef result2;
150 TF_EXPECT_OK(ReadTextOrBinaryProto(env_, filename, &result2));
151 EXPECT_EQ(result2.DebugString(), proto.DebugString());
152 }
153
TEST_F(DefaultEnvTest,FileToReadonlyMemoryRegion)154 TEST_F(DefaultEnvTest, FileToReadonlyMemoryRegion) {
155 for (const int length : {1, 1212, 2553, 4928, 8196, 9000, (1 << 20) - 1,
156 1 << 20, (1 << 20) + 1}) {
157 const string filename =
158 io::JoinPath(BaseDir(), strings::StrCat("file", length));
159
160 // Write a file with the given length
161 const string input = CreateTestFile(env_, filename, length);
162
163 // Create the region.
164 std::unique_ptr<ReadOnlyMemoryRegion> region;
165 TF_EXPECT_OK(env_->NewReadOnlyMemoryRegionFromFile(filename, ®ion));
166 ASSERT_NE(region, nullptr);
167 EXPECT_EQ(length, region->length());
168 EXPECT_EQ(input, string(reinterpret_cast<const char*>(region->data()),
169 region->length()));
170 FileStatistics stat;
171 TF_EXPECT_OK(env_->Stat(filename, &stat));
172 EXPECT_EQ(length, stat.length);
173 EXPECT_FALSE(stat.is_directory);
174 }
175 }
176
TEST_F(DefaultEnvTest,DeleteRecursively)177 TEST_F(DefaultEnvTest, DeleteRecursively) {
178 // Build a directory structure rooted at root_dir.
179 // root_dir -> dirs: child_dir1, child_dir2; files: root_file1, root_file2
180 // child_dir1 -> files: child1_file1
181 // child_dir2 -> empty
182 const string parent_dir = io::JoinPath(BaseDir(), "root_dir");
183 const string child_dir1 = io::JoinPath(parent_dir, "child_dir1");
184 const string child_dir2 = io::JoinPath(parent_dir, "child_dir2");
185 TF_EXPECT_OK(env_->CreateDir(parent_dir));
186 const string root_file1 = io::JoinPath(parent_dir, "root_file1");
187 const string root_file2 = io::JoinPath(parent_dir, "root_file2");
188 const string root_file3 = io::JoinPath(parent_dir, ".root_file3");
189 CreateTestFile(env_, root_file1, 100);
190 CreateTestFile(env_, root_file2, 100);
191 CreateTestFile(env_, root_file3, 100);
192 TF_EXPECT_OK(env_->CreateDir(child_dir1));
193 const string child1_file1 = io::JoinPath(child_dir1, "child1_file1");
194 CreateTestFile(env_, child1_file1, 100);
195 TF_EXPECT_OK(env_->CreateDir(child_dir2));
196
197 int64_t undeleted_files, undeleted_dirs;
198 TF_EXPECT_OK(
199 env_->DeleteRecursively(parent_dir, &undeleted_files, &undeleted_dirs));
200 EXPECT_EQ(0, undeleted_files);
201 EXPECT_EQ(0, undeleted_dirs);
202 EXPECT_EQ(error::Code::NOT_FOUND, env_->FileExists(root_file1).code());
203 EXPECT_EQ(error::Code::NOT_FOUND, env_->FileExists(root_file2).code());
204 EXPECT_EQ(error::Code::NOT_FOUND, env_->FileExists(root_file3).code());
205 EXPECT_EQ(error::Code::NOT_FOUND, env_->FileExists(child1_file1).code());
206 }
207
TEST_F(DefaultEnvTest,DeleteRecursivelyFail)208 TEST_F(DefaultEnvTest, DeleteRecursivelyFail) {
209 // Try to delete a non-existent directory.
210 const string parent_dir = io::JoinPath(BaseDir(), "root_dir");
211
212 int64_t undeleted_files, undeleted_dirs;
213 Status s =
214 env_->DeleteRecursively(parent_dir, &undeleted_files, &undeleted_dirs);
215 EXPECT_EQ(error::Code::NOT_FOUND, s.code());
216 EXPECT_EQ(0, undeleted_files);
217 EXPECT_EQ(1, undeleted_dirs);
218 }
219
TEST_F(DefaultEnvTest,RecursivelyCreateDir)220 TEST_F(DefaultEnvTest, RecursivelyCreateDir) {
221 const string create_path = io::JoinPath(BaseDir(), "a", "b", "c", "d");
222 TF_CHECK_OK(env_->RecursivelyCreateDir(create_path));
223 TF_CHECK_OK(env_->RecursivelyCreateDir(create_path)); // repeat creation.
224 TF_EXPECT_OK(env_->FileExists(create_path));
225 }
226
TEST_F(DefaultEnvTest,RecursivelyCreateDirEmpty)227 TEST_F(DefaultEnvTest, RecursivelyCreateDirEmpty) {
228 TF_CHECK_OK(env_->RecursivelyCreateDir(""));
229 }
230
TEST_F(DefaultEnvTest,RecursivelyCreateDirSubdirsExist)231 TEST_F(DefaultEnvTest, RecursivelyCreateDirSubdirsExist) {
232 // First create a/b.
233 const string subdir_path = io::JoinPath(BaseDir(), "a", "b");
234 TF_CHECK_OK(env_->CreateDir(io::JoinPath(BaseDir(), "a")));
235 TF_CHECK_OK(env_->CreateDir(subdir_path));
236 TF_EXPECT_OK(env_->FileExists(subdir_path));
237
238 // Now try to recursively create a/b/c/d/
239 const string create_path = io::JoinPath(BaseDir(), "a", "b", "c", "d");
240 TF_CHECK_OK(env_->RecursivelyCreateDir(create_path));
241 TF_CHECK_OK(env_->RecursivelyCreateDir(create_path)); // repeat creation.
242 TF_EXPECT_OK(env_->FileExists(create_path));
243 TF_EXPECT_OK(env_->FileExists(io::JoinPath(BaseDir(), "a", "b", "c")));
244 }
245
TEST_F(DefaultEnvTest,LocalFileSystem)246 TEST_F(DefaultEnvTest, LocalFileSystem) {
247 // Test filename with file:// syntax.
248 int expected_num_files = 0;
249 std::vector<string> matching_paths;
250 for (const int length : {0, 1, 1212, 2553, 4928, 8196, 9000, (1 << 20) - 1,
251 1 << 20, (1 << 20) + 1}) {
252 string filename = io::JoinPath(BaseDir(), strings::StrCat("len", length));
253
254 filename = strings::StrCat("file://", filename);
255
256 // Write a file with the given length
257 const string input = CreateTestFile(env_, filename, length);
258 ++expected_num_files;
259
260 // Ensure that GetMatchingPaths works as intended.
261 TF_EXPECT_OK(env_->GetMatchingPaths(
262 // Try it with the "file://" URI scheme.
263 strings::StrCat("file://", io::JoinPath(BaseDir(), "l*")),
264 &matching_paths));
265 EXPECT_EQ(expected_num_files, matching_paths.size());
266 TF_EXPECT_OK(env_->GetMatchingPaths(
267 // Try it without any URI scheme.
268 io::JoinPath(BaseDir(), "l*"), &matching_paths));
269 EXPECT_EQ(expected_num_files, matching_paths.size());
270
271 // Read the file back and check equality
272 string output;
273 TF_EXPECT_OK(ReadFileToString(env_, filename, &output));
274 EXPECT_EQ(length, output.size());
275 EXPECT_EQ(input, output);
276
277 FileStatistics stat;
278 TF_EXPECT_OK(env_->Stat(filename, &stat));
279 EXPECT_EQ(length, stat.length);
280 EXPECT_FALSE(stat.is_directory);
281 }
282 }
283
TEST_F(DefaultEnvTest,SleepForMicroseconds)284 TEST_F(DefaultEnvTest, SleepForMicroseconds) {
285 const int64_t start = env_->NowMicros();
286 const int64_t sleep_time = 1e6 + 5e5;
287 env_->SleepForMicroseconds(sleep_time);
288 const int64_t delta = env_->NowMicros() - start;
289
290 // Subtract 200 from the sleep_time for this check because NowMicros can
291 // sometimes give slightly inconsistent values between the start and the
292 // finish (e.g. because the two calls run on different CPUs).
293 EXPECT_GE(delta, sleep_time - 200);
294 }
295
296 class TmpDirFileSystem : public NullFileSystem {
297 public:
298 TF_USE_FILESYSTEM_METHODS_WITH_NO_TRANSACTION_SUPPORT;
299
FileExists(const string & dir,TransactionToken * token)300 Status FileExists(const string& dir, TransactionToken* token) override {
301 StringPiece scheme, host, path;
302 io::ParseURI(dir, &scheme, &host, &path);
303 if (path.empty()) return errors::NotFound(dir, " not found");
304 // The special "flushed" file exists only if the filesystem's caches have
305 // been flushed.
306 if (path == "/flushed") {
307 if (flushed_) {
308 return OkStatus();
309 } else {
310 return errors::NotFound("FlushCaches() not called yet");
311 }
312 }
313 return Env::Default()->FileExists(io::JoinPath(BaseDir(), path));
314 }
315
CreateDir(const string & dir,TransactionToken * token)316 Status CreateDir(const string& dir, TransactionToken* token) override {
317 StringPiece scheme, host, path;
318 io::ParseURI(dir, &scheme, &host, &path);
319 if (scheme != "tmpdirfs") {
320 return errors::FailedPrecondition("scheme must be tmpdirfs");
321 }
322 if (host != "testhost") {
323 return errors::FailedPrecondition("host must be testhost");
324 }
325 Status status = Env::Default()->CreateDir(io::JoinPath(BaseDir(), path));
326 if (status.ok()) {
327 // Record that we have created this directory so `IsDirectory` works.
328 created_directories_.push_back(std::string(path));
329 }
330 return status;
331 }
332
IsDirectory(const string & dir,TransactionToken * token)333 Status IsDirectory(const string& dir, TransactionToken* token) override {
334 StringPiece scheme, host, path;
335 io::ParseURI(dir, &scheme, &host, &path);
336 for (const auto& existing_dir : created_directories_)
337 if (existing_dir == path) return OkStatus();
338 return errors::NotFound(dir, " not found");
339 }
340
FlushCaches(TransactionToken * token)341 void FlushCaches(TransactionToken* token) override { flushed_ = true; }
342
343 private:
344 bool flushed_ = false;
345 std::vector<std::string> created_directories_ = {"/"};
346 };
347
348 REGISTER_FILE_SYSTEM("tmpdirfs", TmpDirFileSystem);
349
TEST_F(DefaultEnvTest,FlushFileSystemCaches)350 TEST_F(DefaultEnvTest, FlushFileSystemCaches) {
351 Env* env = Env::Default();
352 const string flushed =
353 strings::StrCat("tmpdirfs://", io::JoinPath("testhost", "flushed"));
354 EXPECT_EQ(error::Code::NOT_FOUND, env->FileExists(flushed).code());
355 TF_EXPECT_OK(env->FlushFileSystemCaches());
356 TF_EXPECT_OK(env->FileExists(flushed));
357 }
358
TEST_F(DefaultEnvTest,RecursivelyCreateDirWithUri)359 TEST_F(DefaultEnvTest, RecursivelyCreateDirWithUri) {
360 Env* env = Env::Default();
361 const string create_path = strings::StrCat(
362 "tmpdirfs://", io::JoinPath("testhost", "a", "b", "c", "d"));
363 EXPECT_EQ(error::Code::NOT_FOUND, env->FileExists(create_path).code());
364 TF_CHECK_OK(env->RecursivelyCreateDir(create_path));
365 TF_CHECK_OK(env->RecursivelyCreateDir(create_path)); // repeat creation.
366 TF_EXPECT_OK(env->FileExists(create_path));
367 }
368
TEST_F(DefaultEnvTest,GetExecutablePath)369 TEST_F(DefaultEnvTest, GetExecutablePath) {
370 Env* env = Env::Default();
371 TF_EXPECT_OK(env->FileExists(env->GetExecutablePath()));
372 }
373
TEST_F(DefaultEnvTest,LocalTempFilename)374 TEST_F(DefaultEnvTest, LocalTempFilename) {
375 Env* env = Env::Default();
376 string filename;
377 EXPECT_TRUE(env->LocalTempFilename(&filename));
378 EXPECT_FALSE(env->FileExists(filename).ok());
379
380 // Write something to the temporary file.
381 std::unique_ptr<WritableFile> file_to_write;
382 TF_CHECK_OK(env->NewWritableFile(filename, &file_to_write));
383 #if defined(PLATFORM_GOOGLE)
384 TF_CHECK_OK(file_to_write->Append("Nu"));
385 TF_CHECK_OK(file_to_write->Append(absl::Cord("ll")));
386 #else
387 // TODO(ebrevdo): Remove this version.
388 TF_CHECK_OK(file_to_write->Append("Null"));
389 #endif
390 TF_CHECK_OK(file_to_write->Close());
391 TF_CHECK_OK(env->FileExists(filename));
392
393 // Open the file in append mode, check that Tell() reports the appropriate
394 // offset.
395 std::unique_ptr<WritableFile> file_to_append;
396 TF_CHECK_OK(env->NewAppendableFile(filename, &file_to_append));
397 int64_t pos;
398 TF_CHECK_OK(file_to_append->Tell(&pos));
399 ASSERT_EQ(4, pos);
400
401 // Read from the temporary file and check content.
402 std::unique_ptr<RandomAccessFile> file_to_read;
403 TF_CHECK_OK(env->NewRandomAccessFile(filename, &file_to_read));
404 StringPiece content;
405 char scratch[1024];
406 CHECK_EQ(
407 error::OUT_OF_RANGE,
408 file_to_read->Read(/*offset=*/0, /*n=*/1024, &content, scratch).code());
409 EXPECT_EQ("Null", content);
410
411 // Delete the temporary file.
412 TF_CHECK_OK(env->DeleteFile(filename));
413 EXPECT_FALSE(env->FileExists(filename).ok());
414 }
415
TEST_F(DefaultEnvTest,CreateUniqueFileName)416 TEST_F(DefaultEnvTest, CreateUniqueFileName) {
417 Env* env = Env::Default();
418
419 string prefix = "tempfile-prefix-";
420 string suffix = ".tmp";
421 string filename = prefix;
422
423 EXPECT_TRUE(env->CreateUniqueFileName(&filename, suffix));
424
425 EXPECT_TRUE(absl::StartsWith(filename, prefix));
426 EXPECT_TRUE(str_util::EndsWith(filename, suffix));
427 }
428
TEST_F(DefaultEnvTest,GetProcessId)429 TEST_F(DefaultEnvTest, GetProcessId) {
430 Env* env = Env::Default();
431 EXPECT_NE(env->GetProcessId(), 0);
432 }
433
TEST_F(DefaultEnvTest,GetThreadInformation)434 TEST_F(DefaultEnvTest, GetThreadInformation) {
435 Env* env = Env::Default();
436 // TODO(fishx): Turn on this test for Apple.
437 #if !defined(__APPLE__)
438 EXPECT_NE(env->GetCurrentThreadId(), 0);
439 #endif
440 string thread_name;
441 bool res = env->GetCurrentThreadName(&thread_name);
442 #if defined(PLATFORM_WINDOWS) || defined(__ANDROID__)
443 EXPECT_FALSE(res);
444 #elif !defined(__APPLE__)
445 EXPECT_TRUE(res);
446 EXPECT_GT(thread_name.size(), 0);
447 #endif
448 }
449
TEST_F(DefaultEnvTest,GetChildThreadInformation)450 TEST_F(DefaultEnvTest, GetChildThreadInformation) {
451 Env* env = Env::Default();
452 Thread* child_thread = env->StartThread({}, "tf_child_thread", [env]() {
453 // TODO(fishx): Turn on this test for Apple.
454 #if !defined(__APPLE__)
455 EXPECT_NE(env->GetCurrentThreadId(), 0);
456 #endif
457 string thread_name;
458 bool res = env->GetCurrentThreadName(&thread_name);
459 EXPECT_TRUE(res);
460 ExpectHasSubstr(thread_name, "tf_child_thread");
461 });
462 delete child_thread;
463 }
464
465 } // namespace tensorflow
466