Skip to content

Commit

Permalink
Added a query function WithoutTrailingSlashes(). Updated Host::FetchS…
Browse files Browse the repository at this point in the history
…ingleItemListing() to use modern query functions. Added a unit test suite for vfs::Host, mocking-based. Rebuilt gmock with C++23.
  • Loading branch information
mikekazakov committed Dec 19, 2024
1 parent 102de54 commit e7e260f
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 19 deletions.
1 change: 1 addition & 0 deletions 3rd_Party/googletest/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ cmake \
-D CMAKE_OSX_ARCHITECTURES="arm64;x86_64" \
-D CMAKE_OSX_DEPLOYMENT_TARGET="10.15" \
-D CMAKE_CXX_FLAGS="-fvisibility=hidden -flto -Os" \
-D CMAKE_CXX_STANDARD="23" \
..
make -j

Expand Down
Binary file modified 3rd_Party/googletest/lib/libgmock.a
Binary file not shown.
Binary file modified 3rd_Party/googletest/lib/libgmock_main.a
Binary file not shown.
Binary file modified 3rd_Party/googletest/lib/libgtest.a
Binary file not shown.
Binary file modified 3rd_Party/googletest/lib/libgtest_main.a
Binary file not shown.
3 changes: 3 additions & 0 deletions Source/Utility/include/Utility/PathManip.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ struct PathManip {
// Returns true if the path ends with a forward slash.
static bool HasTrailingSlash(std::string_view _path) noexcept;

// Returns the path without trailing slashes. NB! The root path "/" will be returned as-is.
static std::string_view WithoutTrailingSlashes(std::string_view _path) noexcept;

// Returns the filename portion of the path.
static std::string_view Filename(std::string_view _path) noexcept;

Expand Down
8 changes: 8 additions & 0 deletions Source/Utility/source/PathManip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ bool PathManip::HasTrailingSlash(std::string_view _path) noexcept
return _path.ends_with('/');
}

std::string_view PathManip::WithoutTrailingSlashes(std::string_view _path) noexcept
{
while( _path.size() > 1 && _path.back() == '/' ) {
_path.remove_suffix(1);
}
return _path;
}

std::string_view PathManip::Filename(std::string_view _path) noexcept
{
const char *const first = _path.data();
Expand Down
27 changes: 27 additions & 0 deletions Source/Utility/tests/PathManip_UT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,30 @@ TEST_CASE(PREFIX "HasTrailingSlash")
CHECK(PM::HasTrailingSlash(tc.path) == tc.expected);
}
}

TEST_CASE(PREFIX "WithoutTrailingSlashes")
{
struct TC {
std::string_view path;
std::string_view expected;
} const tcs[] = {
{.path = "", .expected = ""},
{.path = "/", .expected = "/"},
{.path = "//", .expected = "/"},
{.path = "///", .expected = "/"},
{.path = "/a", .expected = "/a"},
{.path = "/a/", .expected = "/a"},
{.path = "/a//", .expected = "/a"},
{.path = "/a///", .expected = "/a"},
{.path = "/a/b", .expected = "/a/b"},
{.path = "/a/b/", .expected = "/a/b"},
{.path = "/a/b//", .expected = "/a/b"},
{.path = "a", .expected = "a"},
{.path = "a/", .expected = "a"},
{.path = "a//", .expected = "a"},
};
for( auto &tc : tcs ) {
INFO(tc.path);
CHECK(PM::WithoutTrailingSlashes(tc.path) == tc.expected);
}
}
6 changes: 5 additions & 1 deletion Source/VFS/VFS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
CF26DE3621E297AE003F0E93 /* EasyOps_UT.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF26DE3521E297AE003F0E93 /* EasyOps_UT.mm */; };
CF3989B32B416F84006103C1 /* libBase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF3989B22B416F84006103C1 /* libBase.a */; };
CF3989B42B416F89006103C1 /* libBase.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CF3989B22B416F84006103C1 /* libBase.a */; };
CF3BFC6B2D143F3300105999 /* Host_UT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CF3BFC6A2D143F3300105999 /* Host_UT.cpp */; };
CF4600722560579F0095FC73 /* VFSFile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CF69D0111DA22BE800992B84 /* VFSFile.cpp */; };
CF4600732560579F0095FC73 /* Listing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CF69D0131DA22BE800992B84 /* Listing.cpp */; };
CF4600742560579F0095FC73 /* SearchForFiles.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CF24E1F922901C6800C166FA /* SearchForFiles.cpp */; };
Expand Down Expand Up @@ -187,6 +188,7 @@
CF26DE2321D28754003F0E93 /* SearchInFile_UT.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SearchInFile_UT.cpp; path = tests/SearchInFile_UT.cpp; sourceTree = SOURCE_ROOT; };
CF26DE3521E297AE003F0E93 /* EasyOps_UT.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = EasyOps_UT.mm; path = tests/EasyOps_UT.mm; sourceTree = SOURCE_ROOT; };
CF3989B22B416F84006103C1 /* libBase.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libBase.a; sourceTree = BUILT_PRODUCTS_DIR; };
CF3BFC6A2D143F3300105999 /* Host_UT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Host_UT.cpp; path = tests/Host_UT.cpp; sourceTree = SOURCE_ROOT; };
CF3E2F841F60DF08001BFFCE /* Requests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Requests.cpp; path = source/NetWebDAV/Requests.cpp; sourceTree = "<group>"; };
CF3E2F851F60DF08001BFFCE /* Requests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Requests.h; path = source/NetWebDAV/Requests.h; sourceTree = "<group>"; };
CF460065256057250095FC73 /* libVFS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libVFS.a; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -386,15 +388,16 @@
children = (
CF26DE3521E297AE003F0E93 /* EasyOps_UT.mm */,
CF26DE0E21CFA2CC003F0E93 /* FileWindow_UT.mm */,
CF3BFC6A2D143F3300105999 /* Host_UT.cpp */,
CF1847021E41C86D008B7C9F /* Info.plist */,
CFE08AE823CB2D83007E99B8 /* ListingInput_UT.cpp */,
CF2343ED22CD31F300F516CB /* NetSFTP */,
CF24E1FD2290200400C166FA /* SearchForFiles_IT.cpp */,
CF26DE2321D28754003F0E93 /* SearchInFile_UT.cpp */,
CFE08AEA23CFAFD8007E99B8 /* TestEnv.h */,
CFE08AEB23CFAFD8007E99B8 /* TestEnv.mm */,
CF26DE2021D2864D003F0E93 /* Tests.cpp */,
CF26DE1F21D2864D003F0E93 /* Tests.h */,
CF26DE2021D2864D003F0E93 /* Tests.cpp */,
CF18470A1E41C8A5008B7C9F /* VFSArchive_IT.mm */,
CFCB68B82886075900086E40 /* VFSArchive_PT.mm */,
CFCB68D2289089BF00086E40 /* VFSArchive_UT.cpp */,
Expand Down Expand Up @@ -808,6 +811,7 @@
files = (
CFCB684F28423A1300086E40 /* VFSError_UT.mm in Sources */,
CF26DE3621E297AE003F0E93 /* EasyOps_UT.mm in Sources */,
CF3BFC6B2D143F3300105999 /* Host_UT.cpp in Sources */,
CFAB6D62258A555A00397DB5 /* FileWindow_UT.mm in Sources */,
CF2343EF22CD321300F516CB /* KeyValidator_UT.cpp in Sources */,
CF824F69279F622900C4F29C /* VFSArchiveRaw_UT.cpp in Sources */,
Expand Down
27 changes: 13 additions & 14 deletions Source/VFS/source/Host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,20 +394,20 @@ int Host::FetchSingleItemListing(std::string_view _path,
if( _cancel_checker && _cancel_checker() )
return VFSError::Cancelled;

// TODO: rewriting without using C-style strings
char path[MAXPATHLEN];
char directory[MAXPATHLEN];
char filename[MAXPATHLEN];
memcpy(path, _path.data(), _path.length());
path[_path.length()] = 0;

if( !EliminateTrailingSlashInPath(path) || !GetDirectoryContainingItemFromPath(path, directory) ||
!GetFilenameFromPath(path, filename) )
const std::string_view directory = utility::PathManip::Parent(_path);
if( directory.empty() )
return VFSError::InvalidCall;

VFSStat lstat;
const std::string_view filename = utility::PathManip::Filename(_path);
if( filename.empty() )
return VFSError::InvalidCall;

const int ret = Stat(_path, lstat, VFSFlags::F_NoFollow);
const std::string_view path_wo_trailing_slash = utility::PathManip::WithoutTrailingSlashes(_path);
if( path_wo_trailing_slash.empty() )
return VFSError::InvalidCall;

VFSStat lstat;
const int ret = Stat(path_wo_trailing_slash, lstat, VFSFlags::F_NoFollow);
if( ret != 0 )
return ret;

Expand Down Expand Up @@ -447,16 +447,15 @@ int Host::FetchSingleItemListing(std::string_view _path,
if( listing_source.unix_types[0] == DT_LNK ) {
// read an actual link path
char linkpath[MAXPATHLEN];
if( ReadSymlink(path, linkpath, MAXPATHLEN) == 0 )
if( ReadSymlink(path_wo_trailing_slash, linkpath, MAXPATHLEN) == 0 )
listing_source.symlinks.insert(0, linkpath);

// stat the target file
VFSStat stat;
if( Stat(_path, stat, 0) == 0 ) {
if( Stat(path_wo_trailing_slash, stat, 0) == 0 ) {
listing_source.unix_modes[0] = stat.mode;
listing_source.unix_flags[0] = stat.flags;
listing_source.uids[0] = stat.uid;
;
listing_source.gids[0] = stat.gid;
listing_source.sizes[0] = stat.size;
}
Expand Down
69 changes: 69 additions & 0 deletions Source/VFS/tests/Host_UT.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (C) 2024 Michael Kazakov. Subject to GNU General Public License version 3.
#include "Tests.h"
#include "TestEnv.h"
#include <VFS/VFS.h>

#define PREFIX "nc::vfs::Host "

using namespace nc;
using namespace nc::vfs;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;

TEST_CASE(PREFIX "FetchSingleItemListing")
{
struct MockHost : Host {
MockHost() : Host("/", nullptr, "mock") {}
MOCK_METHOD(int, Stat, (std::string_view, VFSStat &, unsigned long, const VFSCancelChecker &), (override));
};

auto host = std::make_shared<MockHost>();

VFSListingPtr listing;
SECTION("Not absolute path")
{
REQUIRE(host->FetchSingleItemListing("not absolute path", listing, VFSFlags::None) != VFSError::Ok);
}
SECTION("Single reg-file listing")
{
EXPECT_CALL(*host, Stat(_, _, _, _))
.WillRepeatedly([](std::string_view _path, VFSStat &st, unsigned long, const VFSCancelChecker &) {
REQUIRE(_path == "/my/file.txt");
memset(&st, 0, sizeof(st));
st.size = 42;
st.mode_bits.reg = true;
return 0;
});
REQUIRE(host->FetchSingleItemListing("/my/file.txt", listing, VFSFlags::None) == VFSError::Ok);
REQUIRE(listing);
REQUIRE(listing->Host() == host);
REQUIRE(listing->Count() == 1);
REQUIRE(listing->HasCommonDirectory());
REQUIRE(listing->Directory() == "/my/");
auto item = listing->Item(0);
REQUIRE(item.Directory() == "/my/");
REQUIRE(item.Filename() == "file.txt");
REQUIRE(item.Size() == 42);
REQUIRE(item.UnixMode() == S_IFREG);
}
SECTION("Removes trailing slashes")
{
EXPECT_CALL(*host, Stat(_, _, _, _))
.WillRepeatedly([](std::string_view _path, VFSStat &st, unsigned long, const VFSCancelChecker &) {
REQUIRE(_path == "/my/file.txt");
memset(&st, 0, sizeof(st));
st.size = 42;
st.mode_bits.reg = true;
return 0;
});
REQUIRE(host->FetchSingleItemListing("/my/file.txt///", listing, VFSFlags::None) == VFSError::Ok);
REQUIRE(listing);
REQUIRE(listing->Directory() == "/my/");
auto item = listing->Item(0);
REQUIRE(item.Directory() == "/my/");
REQUIRE(item.Filename() == "file.txt");
}
}
8 changes: 4 additions & 4 deletions Source/VFS/tests/Tests.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (C) 2018-2021 Michael Kazakov. Subject to GNU General Public License version 3.
// Copyright (C) 2018-2024 Michael Kazakov. Subject to GNU General Public License version 3.
#pragma once

#define CATCH_CONFIG_ENABLE_BENCHMARKING
#include <catch2/catch_all.hpp>
#include <filesystem>

// #define GTEST_DONT_DEFINE_FAIL 1
// #define GTEST_DONT_DEFINE_SUCCEED 1
// #include <gmock/gmock.h>
#define GTEST_DONT_DEFINE_FAIL 1
#define GTEST_DONT_DEFINE_SUCCEED 1
#include <gmock/gmock.h>

struct TestDir {
TestDir();
Expand Down

0 comments on commit e7e260f

Please sign in to comment.