Skip to content

Commit 3e8b0e7

Browse files
committed
[sshfs] Add platform unix tests
1 parent 4d1c44a commit 3e8b0e7

File tree

4 files changed

+186
-11
lines changed

4 files changed

+186
-11
lines changed

include/multipass/platform_unix.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ namespace multipass::platform
3232
class SignalWrapper : public Singleton<SignalWrapper>
3333
{
3434
public:
35-
SignalWrapper(const PrivatePass&) noexcept;
35+
SignalWrapper(const PrivatePass&) noexcept;
3636

37-
virtual int mask_signals(int how, const sigset_t* sigset, sigset_t* old_set = nullptr) const;
38-
virtual int send(pthread_t target, int signal) const;
39-
virtual int wait(const sigset_t& sigset, int& got) const;
37+
virtual int mask_signals(int how, const sigset_t* sigset, sigset_t* old_set = nullptr) const;
38+
virtual int send(pthread_t target, int signal) const;
39+
virtual int wait(const sigset_t& sigset, int& got) const;
4040
};
4141

4242
sigset_t make_sigset(const std::vector<int>& sigs);

src/platform/platform_unix.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ int mp::platform::symlink_attr_from(const char* path, sftp_attributes_struct* at
158158
return 0;
159159
}
160160

161-
mp::platform::SignalWrapper::SignalWrapper(const PrivatePass& pass) noexcept
162-
: Singleton(pass){}
163-
161+
mp::platform::SignalWrapper::SignalWrapper(const PrivatePass& pass) noexcept : Singleton(pass)
162+
{
163+
}
164164

165165
int mp::platform::SignalWrapper::mask_signals(int how, const sigset_t* sigset, sigset_t* old_set) const
166166
{
@@ -207,11 +207,8 @@ std::function<std::optional<int>(const std::function<bool()>&)> mp::platform::ma
207207
{
208208
return [sigset = make_and_block_signals({SIGQUIT, SIGTERM, SIGHUP, SIGUSR2}),
209209
period](const std::function<bool()>& condition) -> std::optional<int> {
210-
211210
// create a timer to periodically send SIGUSR2
212-
utils::Timer signal_generator{period, [signalee = pthread_self()] {
213-
MP_POSIX_SIGNAL.send(signalee, SIGUSR2);
214-
}};
211+
utils::Timer signal_generator{period, [signalee = pthread_self()] { MP_POSIX_SIGNAL.send(signalee, SIGUSR2); }};
215212

216213
// wait on signals and condition
217214
int latest_signal = SIGUSR2;

tests/unix/mock_signal_wrapper.h

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (C) Canonical, Ltd.
3+
*
4+
* This program is free software; you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation; version 3.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
#ifndef MULTIPASS_MOCK_SIGNAL_WRAPPER_H
18+
#define MULTIPASS_MOCK_SIGNAL_WRAPPER_H
19+
20+
#include "../common.h"
21+
#include "../mock_singleton_helpers.h"
22+
23+
#include <multipass/platform_unix.h>
24+
25+
namespace multipass::test
26+
{
27+
28+
class MockSignalWrapper : public platform::SignalWrapper
29+
{
30+
public:
31+
using SignalWrapper::SignalWrapper;
32+
33+
MOCK_METHOD(int, mask_signals, (int, const sigset_t*, sigset_t*), (const, override));
34+
MOCK_METHOD(int, send, (pthread_t, int), (const, override));
35+
MOCK_METHOD(int, wait, (const sigset_t&, int&), (const, override));
36+
37+
MP_MOCK_SINGLETON_BOILERPLATE(MockSignalWrapper, platform::SignalWrapper);
38+
};
39+
40+
} // namespace multipass::test
41+
42+
#endif // MULTIPASS_MOCK_SIGNAL_WRAPPER_H

tests/unix/test_platform_unix.cpp

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <tests/temp_file.h>
2222

2323
#include "mock_libc_functions.h"
24+
#include "mock_signal_wrapper.h"
2425

2526
#include <multipass/constants.h>
2627
#include <multipass/format.h>
@@ -142,3 +143,138 @@ TEST_F(TestPlatformUnix, multipassStorageLocationNotSetReturnsEmpty)
142143

143144
EXPECT_TRUE(storage_path.isEmpty());
144145
}
146+
147+
void test_sigset_empty(const sigset_t& set)
148+
{
149+
// there is no standard empty check to try a few different signals
150+
EXPECT_EQ(sigismember(&set, SIGABRT), 0);
151+
EXPECT_EQ(sigismember(&set, SIGALRM), 0);
152+
EXPECT_EQ(sigismember(&set, SIGCHLD), 0);
153+
EXPECT_EQ(sigismember(&set, SIGINT), 0);
154+
EXPECT_EQ(sigismember(&set, SIGSEGV), 0);
155+
EXPECT_EQ(sigismember(&set, SIGKILL), 0);
156+
EXPECT_EQ(sigismember(&set, SIGQUIT), 0);
157+
EXPECT_EQ(sigismember(&set, SIGTERM), 0);
158+
EXPECT_EQ(sigismember(&set, SIGUSR1), 0);
159+
EXPECT_EQ(sigismember(&set, SIGUSR2), 0);
160+
}
161+
162+
bool test_sigset_has(const sigset_t& set, const std::vector<int>& sigs)
163+
{
164+
bool good = true;
165+
for (auto sig : sigs)
166+
{
167+
auto has_sig = sigismember(&set, sig) == 1;
168+
if (!has_sig)
169+
good = false;
170+
171+
EXPECT_TRUE(has_sig);
172+
}
173+
174+
return good;
175+
}
176+
177+
TEST_F(TestPlatformUnix, make_sigset_returns_emptyset)
178+
{
179+
auto set = mp::platform::make_sigset({});
180+
test_sigset_empty(set);
181+
}
182+
183+
TEST_F(TestPlatformUnix, make_sigset_makes_sigset)
184+
{
185+
auto set = mp::platform::make_sigset({SIGINT, SIGUSR2});
186+
187+
// check signals are set
188+
test_sigset_has(set, {SIGINT, SIGUSR2});
189+
190+
// unset set signals
191+
sigdelset(&set, SIGUSR2);
192+
sigdelset(&set, SIGINT);
193+
194+
// check other signals aren't set
195+
test_sigset_empty(set);
196+
}
197+
198+
TEST_F(TestPlatformUnix, make_and_block_signals_works)
199+
{
200+
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();
201+
202+
EXPECT_CALL(
203+
*mock_signals,
204+
mask_signals(SIG_BLOCK, Pointee(Truly([](const auto& set) { return test_sigset_has(set, {SIGINT}); })), _));
205+
206+
auto set = mp::platform::make_and_block_signals({SIGINT});
207+
208+
test_sigset_has(set, {SIGINT});
209+
210+
sigdelset(&set, SIGINT);
211+
test_sigset_empty(set);
212+
}
213+
214+
TEST_F(TestPlatformUnix, make_quit_watchdog_blocks_signals)
215+
{
216+
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();
217+
218+
EXPECT_CALL(*mock_signals,
219+
mask_signals(SIG_BLOCK,
220+
Pointee(Truly([](const auto& set) {
221+
return test_sigset_has(set, {SIGQUIT, SIGTERM, SIGHUP, SIGUSR2});
222+
})),
223+
_));
224+
225+
mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
226+
}
227+
228+
TEST_F(TestPlatformUnix, quit_watchdog_quits_on_condition)
229+
{
230+
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();
231+
232+
EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
233+
EXPECT_CALL(*mock_signals, wait(_, _)).WillRepeatedly(DoAll(SetArgReferee<1>(SIGUSR2), Return(0)));
234+
ON_CALL(*mock_signals, send(pthread_self(), SIGUSR2)).WillByDefault(Return(0));
235+
236+
auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
237+
EXPECT_EQ(watchdog([] { return false; }), std::nullopt);
238+
}
239+
240+
TEST_F(TestPlatformUnix, quit_watchdog_quits_on_signal)
241+
{
242+
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();
243+
244+
EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
245+
EXPECT_CALL(*mock_signals, wait(_, _))
246+
.WillOnce(DoAll(SetArgReferee<1>(SIGUSR2), Return(0)))
247+
.WillOnce(DoAll(SetArgReferee<1>(SIGTERM), Return(0)));
248+
ON_CALL(*mock_signals, send(pthread_self(), SIGUSR2)).WillByDefault(Return(0));
249+
250+
auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
251+
EXPECT_EQ(watchdog([] { return true; }), SIGTERM);
252+
}
253+
254+
TEST_F(TestPlatformUnix, quit_watchdog_signals_itself_asynchronously)
255+
{
256+
auto [mock_signals, guard] = mpt::MockSignalWrapper::inject<StrictMock>();
257+
258+
std::atomic<bool> signaled = false;
259+
std::atomic<int> times = 0;
260+
261+
EXPECT_CALL(*mock_signals, mask_signals(SIG_BLOCK, _, _));
262+
EXPECT_CALL(*mock_signals, wait(_, _))
263+
.WillRepeatedly(DoAll(
264+
[&signaled, &times] {
265+
while (!signaled.load(std::memory_order_acquire))
266+
{
267+
}
268+
times.fetch_add(1, std::memory_order_release);
269+
signaled.store(false, std::memory_order_release);
270+
},
271+
SetArgReferee<1>(SIGUSR2),
272+
Return(0)));
273+
274+
EXPECT_CALL(*mock_signals, send(pthread_self(), SIGUSR2))
275+
.WillRepeatedly(DoAll([&signaled] { signaled.store(true, std::memory_order_release); }, Return(0)));
276+
277+
auto watchdog = mp::platform::make_quit_watchdog(std::chrono::milliseconds{1});
278+
EXPECT_EQ(watchdog([&times] { return times.load(std::memory_order_acquire) < 10; }), std::nullopt);
279+
EXPECT_GE(times.load(std::memory_order_acquire), 10);
280+
}

0 commit comments

Comments
 (0)