~aleteoryx/muditaos

c4fc4e8afdda27cb8110f127ee1ee9d5f4130e4c — Borys Jelenski 5 years ago 06d0a27
[EGD-4761] Add unit tests for littlefs and dual-mount

Additionally, old tests have been refactored for consistency
and readability. Also, files were added to littlefs test image.
M module-vfs/tests/CMakeLists.txt => module-vfs/tests/CMakeLists.txt +9 -0
@@ 37,6 37,15 @@ add_catch2_executable(
        test_lfs_image
)

add_catch2_executable(
    NAME vfs-dualmount
    SRCS 
        ${CMAKE_CURRENT_LIST_DIR}/unittest_filesystem_dualmount.cpp
    LIBS
        module-vfs
    DEPS
        test_lfs_image
)

# prepare test assets
set(ASSETS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test_dir")

M module-vfs/tests/genlfstestimg.sh => module-vfs/tests/genlfstestimg.sh +1 -1
@@ 39,5 39,5 @@ unit: sectors

/dev/sdz1 : start=$SECTOR_START, size=$SECTOR_END, type=9e
==sfdisk
./genlittlefs --image $IMAGE_FILE --block_size=32768  --overwrite  --partition_num 1 -- assets/* .boot.json
./genlittlefs --image $IMAGE_FILE --block_size=32768  --overwrite  --partition_num 1 -- assets/* module-vfs/test_dir/* .boot.json


A module-vfs/tests/test_dir/test_read_1.txt => module-vfs/tests/test_dir/test_read_1.txt +1 -0
@@ 0,0 1,1 @@
0123456789ABCDEF

A module-vfs/tests/unittest_filesystem_dualmount.cpp => module-vfs/tests/unittest_filesystem_dualmount.cpp +136 -0
@@ 0,0 1,136 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

#include <purefs/fs/filesystem.hpp>
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/blkdev/disk_image.hpp>
#include <purefs/fs/drivers/filesystem_vfat.hpp>
#include <purefs/fs/drivers/filesystem_littlefs.hpp>
#include <sys/statvfs.h>
#include <sys/stat.h>
#include <fcntl.h>

namespace
{
    constexpr auto vfat_disk_image = "PurePhone.img";
    constexpr auto lfs_disk_image  = "lfstest.img";

    auto prepare_filesystem(std::string_view vfat_dev_name, std::string_view lfs_dev_name)
        -> std::pair<std::unique_ptr<purefs::fs::filesystem>, std::shared_ptr<purefs::blkdev::disk_manager>>
    {
        using namespace purefs;

        auto dm        = std::make_shared<blkdev::disk_manager>();
        auto vfat_disk = std::make_shared<blkdev::disk_image>(vfat_disk_image);
        auto lfs_disk  = std::make_shared<blkdev::disk_image>(lfs_disk_image);

        if (dm->register_device(vfat_disk, vfat_dev_name) != 0) {
            return {};
        }
        if (dm->register_device(lfs_disk, lfs_dev_name) != 0) {
            return {};
        }

        auto fs_core        = std::make_unique<fs::filesystem>(dm);
        const auto vfs_vfat = std::make_shared<fs::drivers::filesystem_vfat>();
        const auto vfs_lfs  = std::make_shared<fs::drivers::filesystem_littlefs>();

        if (fs_core->register_filesystem("vfat", vfs_vfat) != 0) {
            return {};
        }
        if (fs_core->register_filesystem("littlefs", vfs_lfs) != 0) {
            return {};
        }

        return std::make_pair(std::move(fs_core), std::move(dm));
    }
} // namespace

TEST_CASE("dualmount: Basic mount")
{
    using namespace purefs;

    auto dm        = std::make_shared<blkdev::disk_manager>();
    auto vfat_disk = std::make_shared<blkdev::disk_image>(vfat_disk_image);
    auto lfs_disk  = std::make_shared<blkdev::disk_image>(lfs_disk_image);
    REQUIRE(vfat_disk);
    REQUIRE(lfs_disk);
    REQUIRE(dm->register_device(vfat_disk, "vfat0") == 0);
    REQUIRE(dm->register_device(lfs_disk, "lfs0") == 0);

    fs::filesystem fs_core(dm);
    const auto vfs_vfat = std::make_shared<fs::drivers::filesystem_vfat>();
    const auto vfs_lfs  = std::make_shared<fs::drivers::filesystem_littlefs>();
    REQUIRE(vfs_vfat->mount_count() == 0);
    REQUIRE(vfs_lfs->mount_count() == 0);
    REQUIRE(fs_core.register_filesystem("vfat", vfs_vfat) == 0);
    REQUIRE(fs_core.register_filesystem("littlefs", vfs_lfs) == 0);

    REQUIRE(fs_core.mount("vfat0part0", "/vfat", "vfat") == 0);
    REQUIRE(fs_core.mount("lfs0part0", "/lfs", "littlefs") == 0);
    REQUIRE(vfs_vfat->mount_count() == 1);
    REQUIRE(vfs_lfs->mount_count() == 1);
    REQUIRE(fs_core.umount("/vfat") == 0);
    REQUIRE(fs_core.umount("/lfs") == 0);
}

TEST_CASE("dualmount: API tests")
{
    auto [fs_core, dm] = prepare_filesystem("vfat0", "lfs0");
    REQUIRE(fs_core->mount("vfat0part0", "/vfat", "vfat") == 0);
    REQUIRE(fs_core->mount("lfs0part0", "/lfs", "littlefs") == 0);

    SECTION("Files stat")
    {
        const auto vfat_fd = fs_core->open("/vfat/.boot.json", O_RDONLY, 0);
        REQUIRE(vfat_fd >= 3);
        const auto lfs_fd = fs_core->open("/lfs/test_read_1.txt", O_RDONLY, 0);
        REQUIRE(lfs_fd >= 3);

        struct stat vfat_file_stat;
        struct stat lfs_file_stat;
        REQUIRE(fs_core->fstat(vfat_fd, vfat_file_stat) == 0);
        REQUIRE(fs_core->fstat(lfs_fd, lfs_file_stat) == 0);
        REQUIRE(vfat_file_stat.st_dev != lfs_file_stat.st_dev);

        REQUIRE(fs_core->close(vfat_fd) == 0);
        REQUIRE(fs_core->close(lfs_fd) == 0);
    }
    SECTION("Create, read & write files")
    {
        const auto lfs_fd = fs_core->open("/lfs/test_read_1.txt", O_RDONLY, 0);
        REQUIRE(lfs_fd >= 3);

        static constexpr auto vfat_filename = "/vfat/test_write_tmp_1.txt";
        auto vfat_fd                        = fs_core->open(vfat_filename, O_CREAT | O_RDWR, 0);
        REQUIRE(vfat_fd >= 3);

        const std::string expected = "01234567";

        {
            char buf[64];
            REQUIRE(fs_core->read(lfs_fd, buf, expected.size()) == static_cast<ssize_t>(expected.size()));
            REQUIRE(memcmp(buf, expected.c_str(), expected.size()) == 0);
            REQUIRE(fs_core->write(vfat_fd, buf, expected.size()) == static_cast<ssize_t>(expected.size()));
            REQUIRE(fs_core->close(vfat_fd) == 0);
        }

        {
            char buf[64];
            vfat_fd = fs_core->open(vfat_filename, O_RDWR, 0);
            REQUIRE(vfat_fd >= 3);
            REQUIRE(fs_core->read(vfat_fd, buf, expected.size()) == static_cast<ssize_t>(expected.size()));
            REQUIRE(memcmp(buf, expected.c_str(), expected.size()) == 0);
            REQUIRE(fs_core->close(vfat_fd) == 0);
        }

        REQUIRE(fs_core->close(lfs_fd) == 0);
        fs_core->unlink(vfat_filename);
    }

    REQUIRE(fs_core->umount("/vfat") == 0);
    REQUIRE(fs_core->umount("/lfs") == 0);
}
\ No newline at end of file

M module-vfs/tests/unittest_filesystem_littlefs.cpp => module-vfs/tests/unittest_filesystem_littlefs.cpp +268 -72
@@ 2,107 2,303 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

#include <purefs/fs/filesystem.hpp>
#include <purefs/blkdev/disk_manager.hpp>
#include <purefs/blkdev/disk_image.hpp>
#include <purefs/fs/drivers/filesystem_littlefs.hpp>
#include <sys/statvfs.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>

namespace
{
    constexpr auto disk_image = "lfstest.img";
}

TEST_CASE("Littlefs: Basic mount and functionality")
    auto prepare_filesystem(std::string_view dev_name)
        -> std::pair<std::unique_ptr<purefs::fs::filesystem>, std::shared_ptr<purefs::blkdev::disk_manager>>
    {
        using namespace purefs;

        auto dm   = std::make_shared<blkdev::disk_manager>();
        auto disk = std::make_shared<blkdev::disk_image>(disk_image);

        if (dm->register_device(disk, dev_name) != 0) {
            return {};
        }

        auto fs_core       = std::make_unique<fs::filesystem>(dm);
        const auto vfs_lfs = std::make_shared<fs::drivers::filesystem_littlefs>();

        if (fs_core->register_filesystem("littlefs", vfs_lfs) != 0) {
            return {};
        }

        return std::make_pair(std::move(fs_core), std::move(dm));
    }
} // namespace

TEST_CASE("littlefs: Basic mount and functionality")
{
    using namespace purefs;

    auto dm   = std::make_shared<blkdev::disk_manager>();
    auto disk = std::make_shared<blkdev::disk_image>(disk_image);
    REQUIRE(disk);
    REQUIRE(dm->register_device(disk, "emmc0") == 0);
    purefs::fs::filesystem fscore(dm);
    const auto vfs_vfat = std::make_shared<fs::drivers::filesystem_littlefs>();
    REQUIRE(vfs_vfat->mount_count() == 0);
    auto ret = fscore.register_filesystem("littlefs", vfs_vfat);
    REQUIRE(ret == 0);
    ret = fscore.mount("emmc0part0", "/sys", "littlefs");
    REQUIRE(ret == 0);
    REQUIRE(vfs_vfat->mount_count() == 1);
    REQUIRE(fscore.umount("/ala") == -ENOENT);
    ret = fscore.mount("emmc0part0", "/sys", "vfat");
    REQUIRE(ret == -EBUSY);
    ret = fscore.mount("emmc0part0", "/path", "vfat");
    REQUIRE(ret == -EBUSY);
    ret = fscore.umount("/sys");
    REQUIRE(ret == 0);
    REQUIRE(vfs_vfat->mount_count() == 0);
    ret = fscore.mount("emmc0part0", "/path", "littlefs");
    REQUIRE(ret == 0);
    REQUIRE(vfs_vfat->mount_count() == 1);
    ret = fscore.umount("/path");
    REQUIRE(ret == 0);

    fs::filesystem fscore(dm);
    const auto vfs_lfs = std::make_shared<fs::drivers::filesystem_littlefs>();
    REQUIRE(vfs_lfs->mount_count() == 0);
    REQUIRE(fscore.register_filesystem("littlefs", vfs_lfs) == 0);

    REQUIRE(fscore.mount("emmc0part0", "/sys", "littlefs") == 0);
    REQUIRE(vfs_lfs->mount_count() == 1);

    SECTION("Mount non-existent partition")
    {
        REQUIRE(fscore.mount("dummy0part0", "/dummy", "littlefs") == -ENXIO);
    }
    SECTION("Umount not mounted point")
    {
        REQUIRE(fscore.umount("/ala") == -ENOENT);
    }
    SECTION("Mount when already mounted")
    {
        REQUIRE(fscore.mount("emmc0part0", "/sys", "vfat") == -EBUSY);
    }
    SECTION("Mount invalid filesystem type")
    {
        REQUIRE(fscore.mount("emmc0part0", "/path", "vfat") == -EBUSY);
    }
    SECTION("statvfs")
    {
        struct statvfs ssv;
        REQUIRE(fscore.stat_vfs("/sys/", ssv) == 0);
    }

    REQUIRE(fscore.umount("/sys") == 0);
    REQUIRE(vfs_lfs->mount_count() == 0);

    REQUIRE(fscore.mount("emmc0part0", "/path", "littlefs") == 0);
    REQUIRE(vfs_lfs->mount_count() == 1);
    REQUIRE(fscore.umount("/path") == 0);
}

TEST_CASE("littlefs: Basic API test")
TEST_CASE("littlefs: Read tests")
{
    using namespace purefs;
    auto dm   = std::make_shared<blkdev::disk_manager>();
    auto disk = std::make_shared<blkdev::disk_image>(disk_image);
    REQUIRE(disk);
    REQUIRE(dm->register_device(disk, "emmc0") == 0);
    purefs::fs::filesystem fscore(dm);
    const auto vfs_vfat = std::make_shared<fs::drivers::filesystem_littlefs>();
    REQUIRE(vfs_vfat->mount_count() == 0);
    auto ret = fscore.register_filesystem("littlefs", vfs_vfat);
    REQUIRE(ret == 0);
    // List partitions
    for (const auto &part : dm->partitions("emmc0")) {
        std::cout << part.name << " " << part.bootable << std::endl;
    }
    ret = fscore.mount("emmc0part0", "/sys", "littlefs");
    REQUIRE(ret == 0);
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "littlefs") == 0);

    const auto fd = fs_core->open("/sys/test_read_1.txt", O_RDONLY, 0);
    REQUIRE(fd >= 3);

    static char buf[64];

    SECTION("Simple read")
    {
        struct statvfs ssv;
        ret = fscore.stat_vfs("/sys/", ssv);
        REQUIRE(ret == 0);
        REQUIRE(fs_core->read(fd, buf, 8) == 8);
        REQUIRE(memcmp(buf, "01234567", 8) == 0);
    }
    SECTION("Read after seek")
    {
        REQUIRE(fs_core->seek(fd, 4, SEEK_SET) == 4);
        REQUIRE(fs_core->read(fd, buf, 8) == 8);
        REQUIRE(memcmp(buf, "456789AB", 8) == 0);
    }
    SECTION("File fstat")
    {
        ret = fscore.open("/sys/ala/ma/kota/", 0, 0);
        REQUIRE(ret == -ENOENT);
        // Simple file test
        int hwnd = fscore.open("/sys/fonts/gt_pressura_bold_27.mpf", 0, 0);
        REQUIRE(hwnd >= 3);
        std::cout << "File open handle " << hwnd << std::endl;
        struct stat st;
        ret = fscore.fstat(hwnd, st);
        REQUIRE(ret == 0);
        std::cout << "File size " << st.st_size << std::endl;
        char buf[4096]{};
        ret = fscore.read(hwnd, buf, sizeof buf);
        REQUIRE(ret > 0);
        ret = fscore.close(hwnd);
        REQUIRE(ret == 0);
        REQUIRE(fs_core->fstat(fd, st) == 0);
        REQUIRE(st.st_mode & S_IFREG);
        REQUIRE((st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == (S_IRUSR | S_IRGRP | S_IROTH));
    }

    REQUIRE(fs_core->close(fd) == 0);
    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("littlefs: Write tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "littlefs") == 0);

    SECTION("Create, write & unlink file")
    {
        static constexpr auto filename = "/sys/test_write_tmp_1.txt";
        auto fd                        = fs_core->open(filename, O_CREAT | O_RDWR, 0);
        REQUIRE(fd >= 3);

        const std::string str = "Hello, littlefs!";
        REQUIRE(fs_core->write(fd, str.c_str(), str.length()) == static_cast<ssize_t>(str.length()));

        // reopen the file to flush write buffers
        REQUIRE(fs_core->close(fd) == 0);
        fd = fs_core->open(filename, O_RDONLY, 0);
        REQUIRE(fd >= 3);

        static char buf[64];
        REQUIRE(fs_core->read(fd, buf, str.length()) == static_cast<ssize_t>(str.length()));
        REQUIRE(memcmp(buf, str.c_str(), str.length()) == 0);

        REQUIRE(fs_core->close(fd) == 0);
        REQUIRE(fs_core->unlink(filename) == 0);
    }

    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("littlefs: Read-only filesystem tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "littlefs", purefs::fs::mount_flags::read_only) == 0);

    SECTION("Open and stat file with O_RDONLY")
    {
        const auto fd = fs_core->open("/sys/test_read_1.txt", O_RDONLY, 0);
        REQUIRE(fd >= 3);

        struct stat st;
        int ndirs{};
        // Simple directory test
        auto dirhandle = fscore.diropen("/sys/fonts/");
        REQUIRE(dirhandle);
        REQUIRE(dirhandle->error() == 0);
        for (std::string fnm;;) {
            if (fscore.dirnext(dirhandle, fnm, st) != 0) {
                break;
        REQUIRE(fs_core->fstat(fd, st) == 0);
        REQUIRE(st.st_mode & S_IFREG);
        REQUIRE((st.st_mode & (S_IRUSR | S_IRGRP | S_IROTH)) == (S_IRUSR | S_IRGRP | S_IROTH));
        REQUIRE((st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) == 0);

        REQUIRE(fs_core->close(fd) == 0);
    }
    SECTION("Open file with O_RDWR")
    {
        REQUIRE(fs_core->open("/sys/test_read_1.txt", O_RDWR, 0) == -EACCES);
    }
    SECTION("Create directory")
    {
        REQUIRE(fs_core->mkdir("/sys/tmp_dir", 0) == -EACCES);
    }
    SECTION("Unlink file")
    {
        REQUIRE(fs_core->unlink("/sys/dummy_file.txt") == -EACCES);
    }

    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("littlefs: Directory tests")
{
    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "littlefs") == 0);

    SECTION("Create directory")
    {
        const std::string path = "/sys/test_mkdir_tmp";
        REQUIRE(fs_core->mkdir(path, 0) == 0);

        SECTION("Attempt to create existing directory")
        {
            REQUIRE(fs_core->mkdir(path, 0) == -EEXIST);
        }
        SECTION("Iterate over directory")
        {
            const std::vector<std::string> filenames = {
                "document.txt", "image.jpg", "ringtone.mp3", "data.csv", "picture.png"};
            std::vector<int> fds;
            std::transform(filenames.begin(), filenames.end(), std::back_inserter(fds), [&](const auto &filename) {
                return fs_core->open(path + "/" + filename, O_CREAT | O_RDWR, 0);
            });
            REQUIRE(std::all_of(fds.begin(), fds.end(), [](auto fd) { return fd >= 3; }));
            const auto dh = fs_core->diropen(path);
            REQUIRE(dh);

            SECTION("Simple iterate")
            {
                std::vector<std::string> dir_filenames;
                int dir_status = 0;
                for (;;) {
                    std::string fn;
                    struct stat st;
                    dir_status = fs_core->dirnext(dh, fn, st);

                    if (dir_status == 0) {
                        dir_filenames.push_back(fn);
                    }
                    else {
                        break;
                    }
                }

                REQUIRE(dir_status == -ENODATA);
                REQUIRE(std::all_of(filenames.begin(), filenames.end(), [&dir_filenames](const auto &fn) {
                    return std::find(dir_filenames.begin(), dir_filenames.end(), fn) != dir_filenames.end();
                }));
            }
            SECTION("Directory reset")
            {
                struct stat st;
                std::string first_fn;
                REQUIRE(fs_core->dirnext(dh, first_fn, st) == 0);

                for (std::string tmp_fn; fs_core->dirnext(dh, tmp_fn, st) == 0;)
                    ;
                REQUIRE(fs_core->dirreset(dh) == 0);

                std::string reset_fn;
                REQUIRE(fs_core->dirnext(dh, reset_fn, st) == 0);
                REQUIRE(reset_fn == first_fn);
            }

            REQUIRE(fs_core->dirclose(dh) == 0);
            for (auto fd : fds) {
                REQUIRE(fs_core->close(fd) == 0);
            }
            else {
                std::cout << "name " << fnm << " size " << st.st_size << std::endl;
                ndirs++;
            for (const auto &filename : filenames) {
                REQUIRE(fs_core->unlink(path + "/" + filename) == 0);
            }
        }
        fscore.dirclose(dirhandle);
        REQUIRE(ndirs > 0);

        REQUIRE(fs_core->unlink(path) == 0);
    }

    REQUIRE(fs_core->umount("/sys") == 0);
}

TEST_CASE("littlefs: Remount RO->RW->RW")
{
    using namespace purefs;
    static constexpr auto filename = "/sys/remount_test.txt";

    auto [fs_core, dm] = prepare_filesystem("emmc0");
    REQUIRE(fs_core);
    REQUIRE(fs_core->mount("emmc0part0", "/sys", "littlefs", fs::mount_flags::read_only) == 0);
    REQUIRE(fs_core->open(filename, O_RDWR | O_CREAT, 0) == -EACCES);

    const std::string wr_str = "remount_test";

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount) == 0);
        const auto fd = fs_core->open(filename, O_RDWR | O_CREAT, 0);
        REQUIRE(fd >= 3);
        REQUIRE(fs_core->write(fd, wr_str.c_str(), wr_str.length()) == static_cast<ssize_t>(wr_str.length()));
        REQUIRE(fs_core->close(fd) == 0);
    }

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount | fs::mount_flags::read_only) == 0);
        const auto fd = fs_core->open(filename, O_RDONLY, 0);
        REQUIRE(fd >= 3);
        char buf[64];
        REQUIRE(fs_core->read(fd, buf, wr_str.length()) == static_cast<ssize_t>(wr_str.length()));
        REQUIRE(memcmp(buf, wr_str.c_str(), wr_str.length()) == 0);
        REQUIRE(fs_core->close(fd) == 0);
    }

    {
        REQUIRE(fs_core->mount("", "/sys", "", fs::mount_flags::remount) == 0);
        REQUIRE(fs_core->unlink(filename) == 0);
    }
    ret = fscore.umount("/sys");
    REQUIRE(ret == 0);

    REQUIRE(fs_core->umount("/sys") == 0);
}