Skip to content

Commit

Permalink
[sshfs] Add platform unix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sploder12 committed Dec 17, 2024
1 parent 3d5bfc1 commit 2c6bbdb
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 11 deletions.
8 changes: 4 additions & 4 deletions include/multipass/platform_unix.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ namespace multipass::platform
class SignalWrapper : public Singleton<SignalWrapper>
{
public:
SignalWrapper(const PrivatePass&) noexcept;
SignalWrapper(const PrivatePass&) noexcept;

virtual int mask_signals(int how, const sigset_t* sigset, sigset_t* old_set = nullptr) const;
virtual int send(pthread_t target, int signal) const;
virtual int wait(const sigset_t& sigset, int& got) const;
virtual int mask_signals(int how, const sigset_t* sigset, sigset_t* old_set = nullptr) const;
virtual int send(pthread_t target, int signal) const;
virtual int wait(const sigset_t& sigset, int& got) const;
};

sigset_t make_sigset(const std::vector<int>& sigs);
Expand Down
11 changes: 4 additions & 7 deletions src/platform/platform_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,9 @@ int mp::platform::symlink_attr_from(const char* path, sftp_attributes_struct* at
return 0;
}

mp::platform::SignalWrapper::SignalWrapper(const PrivatePass& pass) noexcept
: Singleton(pass){}

mp::platform::SignalWrapper::SignalWrapper(const PrivatePass& pass) noexcept : Singleton(pass)
{
}

int mp::platform::SignalWrapper::mask_signals(int how, const sigset_t* sigset, sigset_t* old_set) const

Check warning on line 165 in src/platform/platform_unix.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/platform_unix.cpp#L165

Added line #L165 was not covered by tests
{
Expand Down Expand Up @@ -207,11 +207,8 @@ std::function<std::optional<int>(const std::function<bool()>&)> mp::platform::ma
{
return [sigset = make_and_block_signals({SIGQUIT, SIGTERM, SIGHUP, SIGUSR2}),
period](const std::function<bool()>& condition) -> std::optional<int> {

// create a timer to periodically send SIGUSR2
utils::Timer signal_generator{period, [signalee = pthread_self()] {
MP_POSIX_SIGNAL.send(signalee, SIGUSR2);
}};
utils::Timer signal_generator{period, [signalee = pthread_self()] { MP_POSIX_SIGNAL.send(signalee, SIGUSR2); }};

// wait on signals and condition
int latest_signal = SIGUSR2;
Expand Down
42 changes: 42 additions & 0 deletions tests/unix/mock_signal_wrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef MULTIPASS_MOCK_SIGNAL_WRAPPER_H
#define MULTIPASS_MOCK_SIGNAL_WRAPPER_H

#include "../common.h"
#include "../mock_singleton_helpers.h"

#include <multipass/platform_unix.h>

namespace multipass::test
{

class MockSignalWrapper : public platform::SignalWrapper
{
public:
using SignalWrapper::SignalWrapper;

MOCK_METHOD(int, mask_signals, (int, const sigset_t*, sigset_t*), (const, override));
MOCK_METHOD(int, send, (pthread_t, int), (const, override));
MOCK_METHOD(int, wait, (const sigset_t&, int&), (const, override));

MP_MOCK_SINGLETON_BOILERPLATE(MockSignalWrapper, platform::SignalWrapper);
};

} // namespace multipass::test

#endif // MULTIPASS_MOCK_SIGNAL_WRAPPER_H
136 changes: 136 additions & 0 deletions tests/unix/test_platform_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <tests/temp_file.h>

#include "mock_libc_functions.h"
#include "mock_signal_wrapper.h"

#include <multipass/constants.h>
#include <multipass/format.h>
Expand Down Expand Up @@ -142,3 +143,138 @@ TEST_F(TestPlatformUnix, multipassStorageLocationNotSetReturnsEmpty)

EXPECT_TRUE(storage_path.isEmpty());
}

void test_sigset_empty(const sigset_t& set)
{
// there is no standard empty check to try a few different signals
EXPECT_EQ(sigismember(&set, SIGABRT), 0);
EXPECT_EQ(sigismember(&set, SIGALRM), 0);
EXPECT_EQ(sigismember(&set, SIGCHLD), 0);
EXPECT_EQ(sigismember(&set, SIGINT), 0);
EXPECT_EQ(sigismember(&set, SIGSEGV), 0);
EXPECT_EQ(sigismember(&set, SIGKILL), 0);
EXPECT_EQ(sigismember(&set, SIGQUIT), 0);
EXPECT_EQ(sigismember(&set, SIGTERM), 0);
EXPECT_EQ(sigismember(&set, SIGUSR1), 0);
EXPECT_EQ(sigismember(&set, SIGUSR2), 0);
}

bool test_sigset_has(const sigset_t& set, const std::vector<int>& sigs)
{
bool good = true;
for (auto sig : sigs)
{
auto has_sig = sigismember(&set, sig) == 1;
if (!has_sig)
good = false;

EXPECT_TRUE(has_sig);
}

return good;
}

TEST_F(TestPlatformUnix, make_sigset_returns_emptyset)
{
auto set = mp::platform::make_sigset({});
test_sigset_empty(set);
}

TEST_F(TestPlatformUnix, make_sigset_makes_sigset)
{
auto set = mp::platform::make_sigset({SIGINT, SIGUSR2});

// check signals are set
test_sigset_has(set, {SIGINT, SIGUSR2});

// unset set signals
sigdelset(&set, SIGUSR2);
sigdelset(&set, SIGINT);

// check other signals aren't set
test_sigset_empty(set);
}

TEST_F(TestPlatformUnix, make_and_block_signals_works)
{
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();

EXPECT_CALL(
*mock_signals,
mask_signals(SIG_BLOCK, Pointee(Truly([](const auto& set) { return test_sigset_has(set, {SIGINT}); })), _));

auto set = mp::platform::make_and_block_signals({SIGINT});

test_sigset_has(set, {SIGINT});

sigdelset(&set, SIGINT);
test_sigset_empty(set);
}

TEST_F(TestPlatformUnix, make_quit_watchdog_blocks_signals)
{
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();

EXPECT_CALL(*mock_signals,
mask_signals(SIG_BLOCK,
Pointee(Truly([](const auto& set) {
return test_sigset_has(set, {SIGQUIT, SIGTERM, SIGHUP, SIGUSR2});
})),
_));

mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
}

TEST_F(TestPlatformUnix, quit_watchdog_quits_on_condition)
{
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();

EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
EXPECT_CALL(*mock_signals, wait(_, _)).WillRepeatedly(DoAll(SetArgReferee<1>(SIGUSR2), Return(0)));
ON_CALL(*mock_signals, send(pthread_self(), SIGUSR2)).WillByDefault(Return(0));

auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
EXPECT_EQ(watchdog([] { return false; }), std::nullopt);
}

TEST_F(TestPlatformUnix, quit_watchdog_quits_on_signal)
{
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();

EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
EXPECT_CALL(*mock_signals, wait(_, _))
.WillOnce(DoAll(SetArgReferee<1>(SIGUSR2), Return(0)))
.WillOnce(DoAll(SetArgReferee<1>(SIGTERM), Return(0)));
ON_CALL(*mock_signals, send(pthread_self(), SIGUSR2)).WillByDefault(Return(0));

auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
EXPECT_EQ(watchdog([] { return true; }), SIGTERM);
}

TEST_F(TestPlatformUnix, quit_watchdog_signals_itself_asynchronously)
{
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();

std::atomic<bool> signaled = false;
std::atomic<int> times = 0;

EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
EXPECT_CALL(*mock_signals, wait(_, _))
.WillRepeatedly(DoAll(
[&signaled, &times] {
while (!signaled.load(std::memory_order_acquire))
{
}
times.fetch_add(1, std::memory_order_release);
signaled.store(false, std::memory_order_release);
},
SetArgReferee<1>(SIGUSR2),
Return(0)));

EXPECT_CALL(*mock_signals, send(pthread_self(), SIGUSR2))
.WillRepeatedly(DoAll([&signaled] { signaled.store(true, std::memory_order_release); }, Return(0)));

auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
EXPECT_EQ(watchdog([&times] { return times.load(std::memory_order_acquire) < 10; }), std::nullopt);
EXPECT_GE(times.load(std::memory_order_acquire), 10);
}

0 comments on commit 2c6bbdb

Please sign in to comment.