* Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
* Description: File util basic function test.
*/
#include "datasystem/common/util/file_util.h"
#include <cstdlib>
#include <fcntl.h>
#include <fstream>
#include <glob.h>
#include <sys/wait.h>
#include <unistd.h>
#include <securec.h>
#include "ut/common.h"
#include "datasystem/common/util/random_data.h"
DS_DECLARE_string(log_dir);
namespace datasystem {
namespace ut {
namespace {
constexpr const char *TEST_POD_IP_ENV = "DS_TEST_POD_IP";
constexpr const char *TEST_HOST_ID_ENV = "JDOS_HOST_IP";
constexpr const char *TEST_POD_IP_VALUE = "pod-a";
constexpr const char *TEST_HOST_ID_VALUE = "node-a";
constexpr const char *TEST_POD_IP_CRLF_VALUE = "pod-crlf";
constexpr const char *TEST_HOST_ID_CRLF_VALUE = "node-crlf";
constexpr const char *TEST_WORKER_ENV_LOCK_SUFFIX = ".lock";
struct WorkerEnvCase {
const char *env;
const char *key;
const char *value;
};
}
class FileUtilTest : public CommonTest {
public:
FileUtilTest()
: rand_(std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now())
.time_since_epoch()
.count())
{
}
Status CreateTextFile(const std::string &filename, size_t len)
{
std::ofstream osf;
std::string fullPath = FLAGS_log_dir + "/" + filename;
osf.open(fullPath, std::ios::out);
if (!osf.is_open()) {
RETURN_STATUS(StatusCode::K_UNKNOWN_ERROR, "create txt file failed: " + fullPath + std::to_string(errno));
}
std::string content = rand_.GetRandomString(len);
osf << content << std::endl;
osf.close();
return Status::OK();
}
protected:
RandomData rand_;
};
void RemoveIfExists(const std::string &path)
{
if (FileExist(path)) {
DS_ASSERT_OK(RemoveAll(path));
}
}
TEST_F(FileUtilTest, JoinPathTest)
{
std::vector<std::string> absPath = { "", "absolute", "path" };
std::vector<std::string> relativePath = { ".", "relative", "path" };
EXPECT_EQ("/absolute/path", JoinPath(absPath));
EXPECT_EQ("./relative/path", JoinPath(relativePath));
}
TEST_F(FileUtilTest, TestCreateDir)
{
LOG(INFO) << "Test create dir.";
std::string dirname =
FLAGS_log_dir + "/FileUtilTest/" + rand_.GetRandomString(10) + "/" + rand_.GetRandomString(10);
DS_ASSERT_NOT_OK(CreateDir(dirname, false));
DS_ASSERT_OK(CreateDir(dirname, true));
}
TEST_F(FileUtilTest, TestIsSafeDir)
{
LOG(INFO) << "Test safe dir.";
ASSERT_TRUE(IsSafePath("/home"));
ASSERT_TRUE(IsSafePath("/home/../home1"));
ASSERT_TRUE(IsSafePath("/not/exist/path"));
ASSERT_FALSE(IsSafePath("/"));
ASSERT_FALSE(IsSafePath("/home/.."));
ASSERT_FALSE(IsSafePath("/boot"));
ASSERT_FALSE(IsSafePath("/boot/xxx"));
ASSERT_TRUE(IsSafePath("/usr1/xxx"));
ASSERT_TRUE(IsSafePath("/usr1"));
}
TEST_F(FileUtilTest, TestIsDirectory)
{
LOG(INFO) << "Test is dir.";
{
bool exist = false;
DS_ASSERT_OK(IsDirectory("/tmp", exist));
ASSERT_TRUE(exist);
}
{
std::string dirname =
FLAGS_log_dir + "/FileUtilTest/" + rand_.GetRandomString(10) + "/" + rand_.GetRandomString(10);
bool exist = false;
DS_ASSERT_NOT_OK(IsDirectory(dirname, exist));
}
{
std::string filename = rand_.GetRandomString(10);
DS_EXPECT_OK(CreateTextFile(filename, 10));
bool isDir = false;
DS_EXPECT_OK(IsDirectory(FLAGS_log_dir + "/" + filename, isDir));
EXPECT_FALSE(isDir);
DeleteFile(filename);
}
}
TEST_F(FileUtilTest, TestDeleteNotExistFile)
{
LOG(INFO) << "Test delete not exist file.";
std::string filename = "/xxx/yyy/test.txt";
DS_ASSERT_NOT_OK(DeleteFile(filename));
}
TEST_F(FileUtilTest, TestGetStringEnvOrFile)
{
auto dir = JoinPath(FLAGS_log_dir, "WorkerEnvFileTest");
RemoveIfExists(dir);
auto filePath = GetWorkerEnvFilePath(dir);
ASSERT_EQ(GetWorkerEnvFilePath(""), "");
ASSERT_EQ(unsetenv(TEST_POD_IP_ENV), 0);
ASSERT_EQ(unsetenv(TEST_HOST_ID_ENV), 0);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_POD_IP_ENV, "", WORKER_ENV_POD_IP_KEY, "default"), "default");
ASSERT_EQ(setenv(TEST_POD_IP_ENV, TEST_POD_IP_VALUE, 1), 0);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_POD_IP_ENV, filePath, WORKER_ENV_POD_IP_KEY, "default"),
TEST_POD_IP_VALUE);
ASSERT_EQ(setenv(TEST_HOST_ID_ENV, TEST_HOST_ID_VALUE, 1), 0);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_HOST_ID_ENV, filePath, TEST_HOST_ID_ENV, ""), TEST_HOST_ID_VALUE);
ASSERT_EQ(unsetenv(TEST_POD_IP_ENV), 0);
ASSERT_EQ(unsetenv(TEST_HOST_ID_ENV), 0);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_POD_IP_ENV, filePath, WORKER_ENV_POD_IP_KEY, "default"),
TEST_POD_IP_VALUE);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_HOST_ID_ENV, filePath, TEST_HOST_ID_ENV, ""), TEST_HOST_ID_VALUE);
ASSERT_EQ(setenv(TEST_POD_IP_ENV, "", 1), 0);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_POD_IP_ENV, filePath, WORKER_ENV_POD_IP_KEY, "default"),
TEST_POD_IP_VALUE);
std::string content;
DS_ASSERT_OK(ReadWholeFile(filePath, content));
ASSERT_EQ(content, "pod_ip=pod-a\nJDOS_HOST_IP=node-a\n");
DS_ASSERT_OK(AtomicWriteTextFile(filePath, std::string(WORKER_ENV_POD_IP_KEY) + "=" + TEST_POD_IP_CRLF_VALUE
+ "\r\n" + TEST_HOST_ID_ENV + "=" + TEST_HOST_ID_CRLF_VALUE
+ "\r\n"));
ASSERT_EQ(GetStringFromEnvOrFile(TEST_POD_IP_ENV, filePath, WORKER_ENV_POD_IP_KEY, "default"),
TEST_POD_IP_CRLF_VALUE);
ASSERT_EQ(GetStringFromEnvOrFile(TEST_HOST_ID_ENV, filePath, TEST_HOST_ID_ENV, ""), TEST_HOST_ID_CRLF_VALUE);
ASSERT_FALSE(FileExist(filePath + TEST_WORKER_ENV_LOCK_SUFFIX));
RemoveIfExists(dir);
}
TEST_F(FileUtilTest, TestGetStringEnvOrFileConcurrentProcesses)
{
auto dir = JoinPath(FLAGS_log_dir, "WorkerEnvFileConcurrentTest");
RemoveIfExists(dir);
auto filePath = GetWorkerEnvFilePath(dir);
const std::vector<WorkerEnvCase> workerEnvCases = {
{ TEST_POD_IP_ENV, WORKER_ENV_POD_IP_KEY, "pod-a" },
{ TEST_HOST_ID_ENV, TEST_HOST_ID_ENV, "node-a" },
{ TEST_POD_IP_ENV, WORKER_ENV_POD_IP_KEY, "pod-b" },
{ TEST_HOST_ID_ENV, TEST_HOST_ID_ENV, "node-b" },
{ TEST_POD_IP_ENV, WORKER_ENV_POD_IP_KEY, "pod-c" },
{ TEST_HOST_ID_ENV, TEST_HOST_ID_ENV, "node-c" },
};
std::vector<pid_t> children;
children.reserve(workerEnvCases.size());
for (const auto &testCase : workerEnvCases) {
auto pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
(void)setenv(testCase.env, testCase.value, 1);
auto value = GetStringFromEnvOrFile(testCase.env, filePath, testCase.key, "");
_exit(value.empty() ? 1 : 0);
}
children.emplace_back(pid);
}
for (auto pid : children) {
int status = 0;
ASSERT_EQ(waitpid(pid, &status, 0), pid);
ASSERT_TRUE(WIFEXITED(status));
ASSERT_EQ(WEXITSTATUS(status), 0);
}
ASSERT_EQ(unsetenv(TEST_POD_IP_ENV), 0);
ASSERT_EQ(unsetenv(TEST_HOST_ID_ENV), 0);
auto podIp = GetStringFromEnvOrFile(TEST_POD_IP_ENV, filePath, WORKER_ENV_POD_IP_KEY, "");
auto hostId = GetStringFromEnvOrFile(TEST_HOST_ID_ENV, filePath, TEST_HOST_ID_ENV, "");
ASSERT_FALSE(podIp.empty());
ASSERT_FALSE(hostId.empty());
std::string content;
DS_ASSERT_OK(ReadWholeFile(filePath, content));
ASSERT_NE(content.find("pod_ip="), std::string::npos);
ASSERT_NE(content.find("JDOS_HOST_IP="), std::string::npos);
ASSERT_FALSE(FileExist(filePath + TEST_WORKER_ENV_LOCK_SUFFIX));
RemoveIfExists(dir);
}
TEST_F(FileUtilTest, FileLimitReachedException)
{
LOG(INFO) << "Test file limit reached exception scenario.";
LOG(ERROR) << "Start the error log.";
SetFileLimit(34);
int FD[50];
std::string filename[50];
for (int i = 0; i < 50; i++) {
FD[i] = -1;
filename[i] = rand_.GetRandomString(10);
}
std::string folder = FLAGS_log_dir + "/FileLimitTest";
DS_ASSERT_OK(CreateDir(folder));
for (int i = 0; i < 50; i++) {
std::string name = folder + "/" + filename[i];
FD[i] = open(name.c_str(), O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
}
int res = -1;
bool tag = true;
std::string testcase = folder + "/Exception";
Status rc = OpenFile(testcase, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, &res);
if (res != -1) {
tag = false;
close(res);
DS_ASSERT_OK(DeleteFile(testcase));
} else {
ASSERT_EQ(StatusCode::K_FILE_LIMIT_REACHED, rc.GetCode());
SetFileLimit(2048);
DS_ASSERT_OK(OpenFile(testcase, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO, &res));
close(res);
DS_ASSERT_OK(DeleteFile(testcase));
}
for (int i = 0; i < 50; i++) {
std::string name = folder + "/" + filename[i];
if (FD[i] != -1) {
close(FD[i]);
DS_ASSERT_OK(DeleteFile(name));
}
}
rmdir(folder.c_str());
ASSERT_TRUE(tag);
}
TEST_F(FileUtilTest, FileLimitErrors1)
{
Status rc;
struct rlimit rlimSet;
rlimSet.rlim_max = 20;
rlimSet.rlim_cur = 10;
ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &rlimSet), 0);
rc = SetFileLimit(21);
ASSERT_EQ(rc.GetCode(), StatusCode::K_IO_ERROR);
}
TEST_F(FileUtilTest, FileLimitErrors2)
{
struct rlimit rlimGet;
ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &rlimGet), 0);
LOG(INFO) << "rlimGet.rlim_max: " << rlimGet.rlim_max << " rlimGet.rlim_cur: " << rlimGet.rlim_cur;
if (rlimGet.rlim_max == RLIM_INFINITY) {
DS_ASSERT_OK(SetFileLimit(10));
ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &rlimGet), 0);
ASSERT_EQ(rlimGet.rlim_cur, static_cast<rlim_t>(10));
ASSERT_NE(rlimGet.rlim_max, RLIM_INFINITY);
}
}
TEST_F(FileUtilTest, TestChangeFileMod)
{
const std::string filename = rand_.GetRandomString(10);
DS_ASSERT_OK(CreateTextFile(filename, 10));
const mode_t permission = 01760;
const std::string fullPath = FLAGS_log_dir + "/" + filename;
DS_ASSERT_NOT_OK(ChangeFileMod(filename, permission));
DS_ASSERT_OK(ChangeFileMod(fullPath, permission));
struct stat result;
ASSERT_EQ(stat(fullPath.c_str(), &result), 0);
ASSERT_EQ(result.st_mode, static_cast<mode_t>(0101760));
DS_ASSERT_OK(DeleteFile(fullPath));
}
TEST_F(FileUtilTest, TestGetFreeSpace)
{
size_t fsize = GetFreeSpaceBytes("/home");
LOG(INFO) << "Free Size is: " << fsize;
fsize = GetFreeSpaceBytes("/dev/shm");
LOG(INFO) << "Free Size is: " << fsize;
ASSERT_NE(fsize, 0u);
fsize = GetFreeSpaceBytes("./");
LOG(INFO) << "Free Size is: " << fsize;
ASSERT_NE(fsize, 0u);
}
TEST_F(FileUtilTest, TestRemoveAFile)
{
std::string filename("testfile");
DS_ASSERT_OK(CreateTextFile(filename, 10));
ASSERT_TRUE(FileExist(FLAGS_log_dir + "/" + filename));
DS_ASSERT_OK(Remove(FLAGS_log_dir + "/" + filename));
ASSERT_FALSE(FileExist(FLAGS_log_dir + "/" + filename));
}
TEST_F(FileUtilTest, TestRemoveAnDir)
{
std::string dirname = FLAGS_log_dir + "/FileUtilTest/level0/level1";
DS_ASSERT_OK(CreateDir(dirname, true));
DS_ASSERT_NOT_OK(Remove(FLAGS_log_dir + "/FileUtilTest/level0"));
DS_ASSERT_OK(Remove(FLAGS_log_dir + "/FileUtilTest/level0/level1"));
ASSERT_FALSE(FileExist(FLAGS_log_dir + "/FileUtilTest/level0/level1"));
}
TEST_F(FileUtilTest, TestRemoveAll)
{
std::string dirname = FLAGS_log_dir + "/FileUtilTest/level0/level1";
DS_ASSERT_OK(CreateDir(dirname, true));
std::string level0File(FLAGS_log_dir + "/FileUtilTest/level0/file");
std::ofstream create(level0File);
std::string level0FileLink(FLAGS_log_dir + "/FileUtilTest/level0/filelink");
ASSERT_EQ(symlink(level0File.c_str(), level0FileLink.c_str()), 0);
DS_ASSERT_OK(RemoveAll(FLAGS_log_dir + "/FileUtilTest"));
ASSERT_FALSE(FileExist(FLAGS_log_dir + "/FileUtilTest"));
}
TEST_F(FileUtilTest, TestResizeFile)
{
std::string filename("testfile");
std::string fullname(FLAGS_log_dir + "/" + filename);
DS_ASSERT_OK(CreateTextFile(filename, 10));
DS_ASSERT_OK(ResizeFile(fullname, 2048));
EXPECT_EQ(FileSize(fullname), 2048u);
DS_ASSERT_OK(ResizeFile(fullname, 100));
EXPECT_EQ(FileSize(fullname), 100u);
}
TEST_F(FileUtilTest, TestIsEmptyDir)
{
std::string filename("testfile");
DS_ASSERT_OK(CreateTextFile(filename, 10));
EXPECT_FALSE(IsEmptyDir(FLAGS_log_dir + "/" + filename));
EXPECT_FALSE(IsEmptyDir(FLAGS_log_dir));
DS_ASSERT_OK(DeleteFile(FLAGS_log_dir + "/" + filename));
EXPECT_TRUE(IsEmptyDir(FLAGS_log_dir));
}
TEST_F(FileUtilTest, BigFileWriteRead)
{
int fd = -1;
size_t size = 2 * 1024UL * 1024UL * 1024UL;
size_t partSize = 64 * 1024 * 1024;
std::string part = rand_.GetRandomString(partSize);
char *src = (char *)malloc(size);
char *dst = (char *)malloc(size);
size_t remainSize = size;
size_t offSet = 0;
for (size_t i = 0; i < size / partSize; i++) {
ASSERT_EQ(memcpy_s(src + offSet, std::min(remainSize, partSize), part.data(), partSize), EOK);
remainSize -= partSize;
offSet += partSize;
}
std::string filename = "test.bin";
DS_EXPECT_OK(OpenFile(filename, O_RDWR | O_CREAT, 0755, &fd));
DS_ASSERT_OK(WriteFile(fd, src, size, 0));
DS_ASSERT_OK(ReadFile(fd, dst, size, 0));
ASSERT_EQ(memcmp(src, dst, size), 0);
close(fd);
DS_ASSERT_OK(DeleteFile(filename));
}
TEST_F(FileUtilTest, TestMvFileToNewPath)
{
std::string filename = "old_file";
std::string newPath = "./";
DS_ASSERT_OK(CreateTextFile(filename, 10));
std::string realPath = FLAGS_log_dir + "/" + filename;
std::string invalidFilePath = FLAGS_log_dir + "/" + "invalid";
std::string invalidNewPath = "./invalid_dir";
Status rc = MoveFileToNewPath(invalidFilePath, newPath);
ASSERT_EQ(rc.GetCode(), StatusCode::K_INVALID);
rc = MoveFileToNewPath(realPath, invalidNewPath);
ASSERT_EQ(rc.GetCode(), StatusCode::K_INVALID);
DS_ASSERT_OK(MoveFileToNewPath(realPath, newPath));
std::string newFilePath = newPath + filename;
ASSERT_TRUE(FileExist(newFilePath));
DS_ASSERT_OK(DeleteFile(newFilePath));
}
}
}