Pull from FB rev 63ce89e2f2301e6bba44a111cc7d4218022156f6
[folly.git] / folly / experimental / TestUtil.cpp
1 /*
2  * Copyright 2012 Facebook, Inc.
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 "folly/experimental/TestUtil.h"
18
19 #include <stdlib.h>
20 #include <errno.h>
21 #include <stdexcept>
22 #include <system_error>
23
24 #include "folly/Format.h"
25
26 namespace folly {
27 namespace test {
28
29 TemporaryFile::TemporaryFile(const char* prefix, Scope scope,
30                              bool closeOnDestruction)
31   : scope_(scope),
32     closeOnDestruction_(closeOnDestruction) {
33   static const char* suffix = ".XXXXXX";  // per mkstemp(3)
34   if (!prefix || prefix[0] == '\0') {
35     prefix = "temp";
36   }
37   const char* dir = nullptr;
38   if (!strchr(prefix, '/')) {
39     // Not a full path, try getenv("TMPDIR") or "/tmp"
40     dir = getenv("TMPDIR");
41     if (!dir) {
42       dir = "/tmp";
43     }
44     // The "!" is a placeholder to ensure that &(path[0]) is null-terminated.
45     // This is the only standard-compliant way to get at a null-terminated
46     // non-const char string inside a std::string: put the null-terminator
47     // yourself.
48     path_ = format("{}/{}{}!", dir, prefix, suffix).str();
49   } else {
50     path_ = format("{}{}!", prefix, suffix).str();
51   }
52
53   // Replace the '!' with a null terminator, we'll get rid of it later
54   path_[path_.size() - 1] = '\0';
55
56   fd_ = mkstemp(&(path_[0]));
57   if (fd_ == -1) {
58     throw std::system_error(errno, std::system_category(),
59                             format("mkstemp failed: {}", path_).str().c_str());
60   }
61
62   DCHECK_EQ(path_[path_.size() - 1], '\0');
63   path_.erase(path_.size() - 1);
64
65   if (scope_ == Scope::UNLINK_IMMEDIATELY) {
66     if (unlink(path_.c_str()) == -1) {
67       throw std::system_error(errno, std::system_category(),
68                               format("unlink failed: {}", path_).str().c_str());
69     }
70     path_.clear();  // path no longer available or meaningful
71   }
72 }
73
74 const std::string& TemporaryFile::path() const {
75   CHECK(scope_ != Scope::UNLINK_IMMEDIATELY);
76   DCHECK(!path_.empty());
77   return path_;
78 }
79
80 TemporaryFile::~TemporaryFile() {
81   if (fd_ != -1 && closeOnDestruction_) {
82     if (close(fd_) == -1) {
83       PLOG(ERROR) << "close failed";
84     }
85   }
86
87   // If we previously failed to unlink() (UNLINK_IMMEDIATELY), we'll
88   // try again here.
89   if (scope_ != Scope::PERMANENT && !path_.empty()) {
90     if (unlink(path_.c_str()) == -1) {
91       PLOG(ERROR) << "unlink(" << path_ << ") failed";
92     }
93   }
94 }
95
96 }  // namespace test
97 }  // namespace folly