blob: 274063836494ea7e53a54ac2ee87f357921df5b1 [file] [edit]
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Unittests for scandir.
///
//===----------------------------------------------------------------------===//
#include "hdr/types/struct_dirent.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/OSUtil/path.h"
#include "src/dirent/scandir.h"
#include "src/stdio/asprintf.h"
#include "src/stdio/fclose.h"
#include "src/stdio/fopen.h"
#include "src/stdio/remove.h"
#include "src/stdlib/mkdtemp.h"
#include "src/string/strcoll.h"
#include "src/string/strdup.h"
#include "src/unistd/rmdir.h"
#include "test/UnitTest/ErrnoCheckingTest.h"
#include "test/UnitTest/ErrnoSetterMatcher.h"
#include "test/UnitTest/Test.h"
using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
using LlvmLibcScandirTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
constexpr char TEMPLATE[] = "tmp_XXXXXX";
// A dir alwasys has '.' and '..' in it.
constexpr int ENTRIES_MIN = 2;
char *join_path(char *dir, const char *filename) {
char *path = nullptr;
if (LIBC_NAMESPACE::asprintf(&path, "%s%c%s", dir,
LIBC_NAMESPACE::path::SEPARATOR, filename) == -1)
return nullptr;
return path;
}
bool create_empty_file(char *path) {
FILE *file = LIBC_NAMESPACE::fopen(path, "w");
if (file == nullptr)
return false;
if (LIBC_NAMESPACE::fclose(file) == -1)
return false;
return true;
}
char *create_temp_dir() {
char *tmpl = LIBC_NAMESPACE::strdup(libc_make_test_file_path(TEMPLATE));
if (tmpl == nullptr)
return nullptr;
return LIBC_NAMESPACE::mkdtemp(tmpl);
}
bool remove_temp_dir(char *dirpath) {
if (LIBC_NAMESPACE::rmdir(dirpath) == -1)
return false;
::free(dirpath);
return true;
}
int alphasort(const struct dirent **a, const struct dirent **b) {
return LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
}
int omegasort(const struct dirent **a, const struct dirent **b) {
return -LIBC_NAMESPACE::strcoll((*a)->d_name, (*b)->d_name);
}
int skip_hidden(const struct dirent *entry) { return entry->d_name[0] != '.'; }
int skip_as(const struct dirent *entry) { return entry->d_name[0] != 'a'; }
void free_namelist(struct dirent **namelist, int size) {
if (namelist == nullptr)
return;
for (int i = 0; i < size; ++i)
::free(namelist[i]);
::free(namelist);
}
TEST_F(LlvmLibcScandirTest, TestEmptyDir) {
char *dirpath = create_temp_dir();
ASSERT_NE(dirpath, nullptr);
struct dirent **namelist;
ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, nullptr, nullptr),
Succeeds(ENTRIES_MIN));
// Order of namelist is not guaranteed so we can't easily use ASSERT_STREQ
LIBC_NAMESPACE::cpp::string_view first(namelist[0]->d_name);
LIBC_NAMESPACE::cpp::string_view second(namelist[1]->d_name);
ASSERT_TRUE((first == "." && second == "..") ||
(first == ".." && second == "."));
free_namelist(namelist, ENTRIES_MIN);
ASSERT_TRUE(remove_temp_dir(dirpath));
}
TEST_F(LlvmLibcScandirTest, TestFilter) {
char *dirpath = create_temp_dir();
ASSERT_NE(dirpath, nullptr);
struct dirent **namelist;
ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, nullptr),
Succeeds(0));
free_namelist(namelist, 0);
ASSERT_TRUE(remove_temp_dir(dirpath));
}
TEST_F(LlvmLibcScandirTest, TestPartialFilter) {
char *dirpath = create_temp_dir();
ASSERT_NE(dirpath, nullptr);
const char *files_to_create[] = {"a0", "a1", "b0"};
constexpr size_t NUM_FILES =
sizeof(files_to_create) / sizeof(files_to_create[0]);
char *filepaths[NUM_FILES];
for (size_t i = 0; i < NUM_FILES; ++i) {
filepaths[i] = join_path(dirpath, files_to_create[i]);
ASSERT_NE(filepaths[i], nullptr);
ASSERT_TRUE(create_empty_file(filepaths[i]));
}
struct dirent **namelist;
ASSERT_THAT(LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_as, nullptr),
Succeeds(ENTRIES_MIN + 1));
free_namelist(namelist, NUM_FILES);
for (size_t i = 0; i < NUM_FILES; ++i) {
ASSERT_THAT(LIBC_NAMESPACE::remove(filepaths[i]), Succeeds());
::free(filepaths[i]);
}
ASSERT_TRUE(remove_temp_dir(dirpath));
}
TEST_F(LlvmLibcScandirTest, TestDirSorted) {
char *dirpath = create_temp_dir();
ASSERT_NE(dirpath, nullptr);
const char *files_to_create[] = {"d", "a", "1"};
constexpr size_t NUM_FILES =
sizeof(files_to_create) / sizeof(files_to_create[0]);
char *filepaths[NUM_FILES];
for (size_t i = 0; i < NUM_FILES; ++i) {
filepaths[i] = join_path(dirpath, files_to_create[i]);
ASSERT_NE(filepaths[i], nullptr);
ASSERT_TRUE(create_empty_file(filepaths[i]));
}
struct dirent **namelist = nullptr;
ASSERT_THAT(
LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, alphasort),
Succeeds(3));
ASSERT_STREQ(namelist[0]->d_name, "1");
ASSERT_STREQ(namelist[1]->d_name, "a");
ASSERT_STREQ(namelist[2]->d_name, "d");
free_namelist(namelist, NUM_FILES);
// Reverse alphanumeric sort in case the above sorting test passed on chance.
namelist = nullptr;
ASSERT_THAT(
LIBC_NAMESPACE::scandir(dirpath, &namelist, skip_hidden, omegasort),
Succeeds(3));
ASSERT_STREQ(namelist[0]->d_name, "d");
ASSERT_STREQ(namelist[1]->d_name, "a");
ASSERT_STREQ(namelist[2]->d_name, "1");
free_namelist(namelist, NUM_FILES);
for (size_t i = 0; i < NUM_FILES; ++i) {
ASSERT_THAT(LIBC_NAMESPACE::remove(filepaths[i]), Succeeds());
::free(filepaths[i]);
}
ASSERT_TRUE(remove_temp_dir(dirpath));
}
// While this test only checks for one type of ERROR, it really tests
// the error propagation from Dir::open. And as such we don't really
// have to test for every error inherited from Dir::open.
TEST_F(LlvmLibcScandirTest, TestBadDirname) {
struct dirent **namelist;
ASSERT_THAT(LIBC_NAMESPACE::scandir("", &namelist, NULL, NULL),
Fails(ENOENT, -1));
}