Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/multipass/logging/cstring.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CString
{
}

const char* c_str()
const char* c_str() const
{
return data;
}
Expand Down
60 changes: 60 additions & 0 deletions include/multipass/top_catch_all.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2020 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_TOP_CATCH_ALL_H
#define MULTIPASS_TOP_CATCH_ALL_H

#include <multipass/format.h>
#include <multipass/logging/log.h>

namespace multipass
{
/**
* Call f within a try-catch, catching and logging anything that it throws.
*
* @tparam F The type of the callable f. It must be callable and return int.
* @tparam Args The types of f's arguments
* @param log_category The category to use when logging exceptions
* @param f The int-returning function to protect with a catch-all
* @param args The arguments to pass to the function f
* @return The result of f when no exception is thrown, EXIT_FAILURE (from cstdlib) otherwise
*/
template <typename F, typename... Args> // F needs to return int
int top_catch_all(const logging::CString log_category, F f, Args&&... args); // not noexcept because logging isn't
}

template <typename F, typename... Args>
inline int multipass::top_catch_all(const logging::CString log_category, F f, Args&&... args)
{
namespace mpl = multipass::logging;
try
{
return f(std::forward<Args>(args)...);
}
catch (const std::exception& e)
{
mpl::log(mpl::Level::error, log_category, fmt::format("Caught an unhandled exception: {}", e.what()));
return EXIT_FAILURE;
}
catch (...)
{
mpl::log(mpl::Level::error, log_category, "Caught an unknown exception");
return EXIT_FAILURE;
}
}

#endif // MULTIPASS_TOP_CATCH_ALL_H
13 changes: 11 additions & 2 deletions src/client/cli/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017-2019 Canonical, Ltd.
* Copyright (C) 2017-2020 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
Expand All @@ -20,12 +20,15 @@
#include <multipass/cli/client_common.h>
#include <multipass/console.h>
#include <multipass/constants.h>
#include <multipass/top_catch_all.h>

#include <QCoreApplication>

namespace mp = multipass;

int main(int argc, char* argv[])
namespace
{
int main_impl(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(mp::client_name);
Expand All @@ -39,3 +42,9 @@ int main(int argc, char* argv[])

return client.run(QCoreApplication::arguments());
}
} // namespace

int main(int argc, char* argv[])
{
return mp::top_catch_all("client", main_impl, argc, argv);
}
13 changes: 11 additions & 2 deletions src/client/gui/main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Canonical, Ltd.
* Copyright (C) 2019-2020 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
Expand All @@ -18,12 +18,15 @@
#include "client_gui.h"

#include <multipass/cli/client_common.h>
#include <multipass/top_catch_all.h>

#include <QApplication>

namespace mp = multipass;

int main(int argc, char* argv[])
namespace
{
int main_impl(int argc, char* argv[])
{
QApplication app(argc, argv);
app.setApplicationName("multipass-gui");
Expand All @@ -34,3 +37,9 @@ int main(int argc, char* argv[])

return client.run(app.arguments());
}
} // namespace

int main(int argc, char* argv[])
{
return mp::top_catch_all("client", main_impl, argc, argv);
}
15 changes: 7 additions & 8 deletions src/daemon/daemon_main.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017-2019 Canonical, Ltd.
* Copyright (C) 2017-2020 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
Expand Down Expand Up @@ -27,6 +27,7 @@
#include <multipass/name_generator.h>
#include <multipass/platform.h>
#include <multipass/platform_unix.h>
#include <multipass/top_catch_all.h>
#include <multipass/utils.h>
#include <multipass/version.h>
#include <multipass/virtual_machine_factory.h>
Expand Down Expand Up @@ -104,10 +105,7 @@ class UnixSignalHandler
mp::AutoJoinThread signal_handling_thread;
};

} // namespace

int main(int argc, char* argv[]) // clang-format off
try // clang-format on
int main_impl(int argc, char* argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(mp::daemon_name);
Expand All @@ -129,8 +127,9 @@ try // clang-format on
mpl::log(mpl::Level::info, "daemon", "Goodbye!");
return ret;
}
catch (const std::exception& e)
} // namespace

int main(int argc, char* argv[])
{
mpl::log(mpl::Level::error, "daemon", e.what());
return EXIT_FAILURE;
return mp::top_catch_all("daemon", main_impl, argc, argv);
}
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright © 2017-2019 Canonical Ltd.
# Copyright © 2017-2020 Canonical Ltd.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -67,6 +67,7 @@ add_executable(multipass_tests
test_ssh_key_provider.cpp
test_ssh_process.cpp
test_ssh_session.cpp
test_top_catch_all.cpp
test_ubuntu_image_host.cpp
test_utils.cpp
test_with_mocked_bin_path.cpp
Expand Down
39 changes: 39 additions & 0 deletions tests/mock_logger.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2020 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_LOGGER_H
#define MULTIPASS_MOCK_LOGGER_H

#include <multipass/logging/logger.h>

#include <gmock/gmock.h>

namespace multipass
{
namespace test
{
class MockLogger : public multipass::logging::Logger
{
public:
MockLogger() = default;
MOCK_CONST_METHOD3(log, void(multipass::logging::Level level, multipass::logging::CString category,
multipass::logging::CString message));
};
} // namespace test
} // namespace multipass

#endif // MULTIPASS_MOCK_LOGGER_H
132 changes: 132 additions & 0 deletions tests/test_top_catch_all.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* Copyright (C) 2020 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/>.
*
*/

#include "mock_logger.h"

#include <multipass/logging/log.h>
#include <multipass/top_catch_all.h>

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <functional>
#include <memory>
#include <stdexcept>

namespace mp = multipass;
namespace mpl = multipass::logging;
namespace mpt = multipass::test;
using namespace testing;

namespace
{
struct TopCatchAll : public Test
{
void SetUp() override
{
mock_logger = std::make_shared<StrictMock<mpt::MockLogger>>();
mpl::set_logger(mock_logger);
}

void TearDown() override
{
mock_logger.reset();
mpl::set_logger(mock_logger);
}

template <typename Matcher>
auto make_cstring_matcher(const Matcher& matcher)
{
return Property(&mpl::CString::c_str, matcher);
}

auto make_category_matcher()
{
return make_cstring_matcher(StrEq(category.c_str()));
}

const std::string category = "testing";
std::shared_ptr<StrictMock<mpt::MockLogger>> mock_logger = nullptr;
};

struct CustomExceptionForTesting : public std::exception
{
public:
CustomExceptionForTesting() = default;
const char* what() const noexcept override
{
return msg;
}

inline static constexpr const auto msg = "custom";
};

} // namespace

TEST_F(TopCatchAll, calls_function_with_no_args)
{
int ret = 123, got = 0;
EXPECT_NO_THROW(got = mp::top_catch_all("", [ret] { return ret; }););
EXPECT_EQ(got, ret);
}

TEST_F(TopCatchAll, calls_function_with_args)
{
int a = 5, b = 7, got = 0;
EXPECT_NO_THROW(got = mp::top_catch_all("", std::plus<int>{}, a, b););
EXPECT_EQ(got, a + b);
}

TEST_F(TopCatchAll, handles_unknown_error)
{
int got = 0;

EXPECT_CALL(*mock_logger,
log(Eq(mpl::Level::error), make_category_matcher(), make_cstring_matcher(HasSubstr("unknown"))));
EXPECT_NO_THROW(got = mp::top_catch_all(category, [] {
throw 123;
return 0;
}););
EXPECT_EQ(got, EXIT_FAILURE);
}

TEST_F(TopCatchAll, handles_standard_exception)
{
int got = 0;
const std::string emsg = "some error";
const auto msg_matcher = AllOf(HasSubstr("exception"), HasSubstr(emsg.c_str()));

EXPECT_CALL(*mock_logger, log(Eq(mpl::Level::error), make_category_matcher(), make_cstring_matcher(msg_matcher)));
EXPECT_NO_THROW(got = mp::top_catch_all(category, [&emsg] {
throw std::runtime_error{emsg};
return 0;
}););
EXPECT_EQ(got, EXIT_FAILURE);
}

TEST_F(TopCatchAll, handles_custom_exception)
{
int got = 0;
const auto msg_matcher = AllOf(HasSubstr("exception"), HasSubstr(CustomExceptionForTesting::msg));

EXPECT_CALL(*mock_logger, log(Eq(mpl::Level::error), make_category_matcher(), make_cstring_matcher(msg_matcher)));
EXPECT_NO_THROW(got = mp::top_catch_all(category, [] {
throw CustomExceptionForTesting{};
return 42;
}));
EXPECT_EQ(got, EXIT_FAILURE);
}