Skip to content

Commit

Permalink
Merge #1467
Browse files Browse the repository at this point in the history
1467: Mockable standard paths r=townsend2010 a=ricab

Adds a mockable `StandardPaths` singleton, as a thin wrapper around `QStandardPaths`. Implements the corresponding mock with the same approach as `MockSettings`, that is, via:
1. a test environment, to run custom code during test setup and tear down
2. a test event listener, to verify and clear mock expectations on the singleton at the end of each test
3. default operations delegating on the parent

To achieve that, this PR also extracts common stuff into a `MockSingletonHelper`.

This approach enforces accounting of mock expectations in the respective test. It also avoids singleton state leaking from test to test and affecting the results based on execution order. Finally, it ensures that tests only deal with the mock. They use only parent operations that are deemed safe for testing, indirectly, via the mock itself.

Co-authored-by: Ricardo Abreu <ricab@ricabhome.org>
  • Loading branch information
bors[bot] and ricab committed Apr 7, 2020
2 parents e8433ce + e991417 commit 8e085b5
Show file tree
Hide file tree
Showing 19 changed files with 457 additions and 93 deletions.
66 changes: 66 additions & 0 deletions include/multipass/standard_paths.h
@@ -0,0 +1,66 @@
/*
* 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_STANDARD_PATHS_H
#define MULTIPASS_STANDARD_PATHS_H

#include "singleton.h"

#include <QStandardPaths>

namespace multipass
{
class StandardPaths : public Singleton<StandardPaths>
{
public:
// TODO try to replace all the stuff below with `using enum` in C++20 (P1099R5)
using LocateOption = QStandardPaths::LocateOption;
static constexpr auto LocateFile = LocateOption::LocateFile;
static constexpr auto LocateDirectory = LocateOption::LocateDirectory;
using LocateOptions = QStandardPaths::LocateOptions;

using StandardLocation = QStandardPaths::StandardLocation;
static constexpr auto DesktopLocation = StandardLocation::DesktopLocation;
static constexpr auto DocumentsLocation = StandardLocation::DocumentsLocation;
static constexpr auto FontsLocation = StandardLocation::FontsLocation;
static constexpr auto ApplicationsLocation = StandardLocation::ApplicationsLocation;
static constexpr auto MusicLocation = StandardLocation::MusicLocation;
static constexpr auto MoviesLocation = StandardLocation::MoviesLocation;
static constexpr auto PicturesLocation = StandardLocation::PicturesLocation;
static constexpr auto TempLocation = StandardLocation::TempLocation;
static constexpr auto HomeLocation = StandardLocation::HomeLocation;
static constexpr auto DataLocation = StandardLocation::DataLocation;
static constexpr auto CacheLocation = StandardLocation::CacheLocation;
static constexpr auto GenericCacheLocation = StandardLocation::GenericCacheLocation;
static constexpr auto GenericDataLocation = StandardLocation::GenericDataLocation;
static constexpr auto RuntimeLocation = StandardLocation::RuntimeLocation;
static constexpr auto ConfigLocation = StandardLocation::ConfigLocation;
static constexpr auto DownloadLocation = StandardLocation::DownloadLocation;
static constexpr auto GenericConfigLocation = StandardLocation::GenericConfigLocation;
static constexpr auto AppDataLocation = StandardLocation::AppDataLocation;
static constexpr auto AppLocalDataLocation = StandardLocation::AppLocalDataLocation;
static constexpr auto AppConfigLocation = StandardLocation::AppConfigLocation;

StandardPaths(const Singleton<StandardPaths>::PrivatePass&);

virtual QString locate(StandardLocation type, const QString& fileName,
LocateOptions options = LocateOption::LocateFile) const;
virtual QStringList standardLocations(StandardLocation type) const;
virtual QString writableLocation(StandardLocation type) const;
};
} // namespace multipass
#endif // MULTIPASS_STANDARD_PATHS_H
7 changes: 3 additions & 4 deletions src/client/common/client_common.cpp
@@ -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 @@ -17,10 +17,9 @@

#include <multipass/cli/client_common.h>
#include <multipass/platform.h>
#include <multipass/standard_paths.h>
#include <multipass/utils.h>

#include <QStandardPaths>

#include <fmt/ostream.h>
#include <multipass/exceptions/autostart_setup_exception.h>
#include <multipass/logging/log.h>
Expand Down Expand Up @@ -91,7 +90,7 @@ std::string mp::client::get_server_address()

std::unique_ptr<mp::SSLCertProvider> mp::client::get_cert_provider()
{
auto data_dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
auto data_dir = StandardPaths::instance().writableLocation(StandardPaths::AppDataLocation);
auto client_cert_dir = mp::utils::make_dir(data_dir, "client-certificate");
return std::make_unique<mp::SSLCertProvider>(client_cert_dir);
}
Expand Down
7 changes: 4 additions & 3 deletions src/client/gui/gui_cmd.cpp
@@ -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 @@ -23,6 +23,7 @@
#include <multipass/cli/format_utils.h>
#include <multipass/format.h>
#include <multipass/settings.h>
#include <multipass/standard_paths.h>
#include <multipass/version.h>

#include <QHotkey>
Expand Down Expand Up @@ -116,7 +117,7 @@ mp::ReturnCode cmd::GuiCmd::run(mp::ArgParser* parser)
create_menu();
tray_icon.show();

QFile first_run_file(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/first_run");
QFile first_run_file(StandardPaths::instance().writableLocation(StandardPaths::AppDataLocation) + "/first_run");

if (!first_run_file.exists())
{
Expand Down Expand Up @@ -290,7 +291,7 @@ void cmd::GuiCmd::create_menu()

about_client_version.setEnabled(false);
about_daemon_version.setEnabled(false);
about_copyright.setText("Copyright © 2017-2019 Canonical Ltd.");
about_copyright.setText("Copyright © 2017-2020 Canonical Ltd.");
about_copyright.setEnabled(false);

about_menu.insertActions(0, {&autostart_option, &about_client_version, &about_daemon_version, &about_copyright});
Expand Down
6 changes: 3 additions & 3 deletions src/daemon/daemon_config.cpp
Expand Up @@ -28,9 +28,9 @@
#include <multipass/platform.h>
#include <multipass/ssh/openssh_key_provider.h>
#include <multipass/ssl_cert_provider.h>
#include <multipass/standard_paths.h>
#include <multipass/utils.h>

#include <QStandardPaths>
#include <QString>
#include <QUrl>

Expand Down Expand Up @@ -106,9 +106,9 @@ std::unique_ptr<const mp::DaemonConfig> mp::DaemonConfigBuilder::build()
mpl::set_logger(multiplexing_logger);

if (cache_directory.isEmpty())
cache_directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
cache_directory = StandardPaths::instance().writableLocation(StandardPaths::CacheLocation);
if (data_directory.isEmpty())
data_directory = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
data_directory = StandardPaths::instance().writableLocation(StandardPaths::AppDataLocation);
if (url_downloader == nullptr)
url_downloader = std::make_unique<URLDownloader>(cache_directory, std::chrono::seconds{10});
if (factory == nullptr)
Expand Down
7 changes: 3 additions & 4 deletions src/platform/platform_linux.cpp
@@ -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 @@ -21,6 +21,7 @@
#include <multipass/logging/log.h>
#include <multipass/platform.h>
#include <multipass/snap_utils.h>
#include <multipass/standard_paths.h>
#include <multipass/utils.h>
#include <multipass/virtual_machine_factory.h>

Expand All @@ -31,8 +32,6 @@
#include "shared/sshfs_server_process_spec.h"
#include <disabled_update_prompt.h>

#include <QStandardPaths>

#include <signal.h>
#include <sys/prctl.h>

Expand All @@ -53,7 +52,7 @@ QString mp::platform::autostart_test_data()

void mp::platform::setup_gui_autostart_prerequisites()
{
const auto config_dir = QDir{QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)};
const auto config_dir = QDir{StandardPaths::instance().writableLocation(StandardPaths::GenericConfigLocation)};
const auto link_dir = QDir{config_dir.absoluteFilePath("autostart")};
mu::link_autostart_file(link_dir, mp::client_name, autostart_filename);
}
Expand Down
10 changes: 5 additions & 5 deletions src/ssh/ssh_session.cpp
Expand Up @@ -15,18 +15,17 @@
*
*/

#include <multipass/ssh/ssh_session.h>

#include <multipass/ssh/ssh_key_provider.h>
#include <multipass/ssh/ssh_session.h>
#include <multipass/ssh/throw_on_error.h>
#include <multipass/standard_paths.h>

#include <libssh/callbacks.h>
#include <libssh/socket.h>

#include <multipass/format.h>

#include <QDir>
#include <QStandardPaths>

#include <sstream>
#include <stdexcept>
Expand All @@ -43,8 +42,9 @@ mp::SSHSession::SSHSession(const std::string& host, int port, const std::string&

const long timeout_secs = std::chrono::duration_cast<std::chrono::seconds>(timeout).count();
const int nodelay{1};
auto ssh_dir =
QDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)).filePath("ssh").toStdString();
auto ssh_dir = QDir(StandardPaths::instance().writableLocation(StandardPaths::AppConfigLocation))
.filePath("ssh")
.toStdString();

set_option(SSH_OPTIONS_HOST, host.c_str());
set_option(SSH_OPTIONS_PORT, &port);
Expand Down
1 change: 1 addition & 0 deletions src/utils/CMakeLists.txt
Expand Up @@ -16,6 +16,7 @@ add_library(utils STATIC
memory_size.cpp
settings.cpp
snap_utils.cpp
standard_paths.cpp
utils.cpp)

target_link_libraries(utils
Expand Down
5 changes: 3 additions & 2 deletions src/utils/settings.cpp
Expand Up @@ -18,11 +18,11 @@
#include <multipass/constants.h>
#include <multipass/platform.h>
#include <multipass/settings.h>
#include <multipass/standard_paths.h>
#include <multipass/utils.h> // TODO move out

#include <QDir>
#include <QSettings>
#include <QStandardPaths>

#include <algorithm>
#include <array>
Expand Down Expand Up @@ -61,7 +61,8 @@ QString file_for(const QString& key) // the key should have passed checks at thi
{
// static consts ensure these stay fixed
static const auto file_pattern = QStringLiteral("%2.%1").arg(file_extension); // note the order
static const auto user_config_path = QDir{QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)};
static const auto user_config_path =
QDir{mp::StandardPaths::instance().writableLocation(mp::StandardPaths::GenericConfigLocation)};
static const auto cli_client_dir_path = QDir{user_config_path.absoluteFilePath(mp::client_name)};
static const auto daemon_dir_path = QDir{mp::platform::daemon_config_home()}; // temporary, replace w/ AppConfigLoc
static const auto client_file_path = cli_client_dir_path.absoluteFilePath(file_pattern.arg(mp::client_name));
Expand Down
40 changes: 40 additions & 0 deletions src/utils/standard_paths.cpp
@@ -0,0 +1,40 @@
/*
* 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 <multipass/standard_paths.h>

namespace mp = multipass;

mp::StandardPaths::StandardPaths(const Singleton<StandardPaths>::PrivatePass& pass)
: Singleton<StandardPaths>::Singleton{pass}
{
}

QString mp::StandardPaths::locate(StandardLocation type, const QString& fileName, LocateOptions options) const
{
return QStandardPaths::locate(type, fileName, options);
}

QStringList mp::StandardPaths::standardLocations(StandardLocation type) const
{
return QStandardPaths::standardLocations(type);
}

QString mp::StandardPaths::writableLocation(StandardLocation type) const
{
return QStandardPaths::writableLocation(type);
}
7 changes: 4 additions & 3 deletions src/utils/utils.cpp
Expand Up @@ -23,13 +23,13 @@
#include <multipass/logging/log.h>
#include <multipass/settings.h>
#include <multipass/ssh/ssh_session.h>
#include <multipass/standard_paths.h>
#include <multipass/utils.h>

#include <QDateTime>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QStandardPaths>
#include <QUuid>
#include <QtGlobal>

Expand Down Expand Up @@ -59,12 +59,13 @@ auto quote_for(const std::string& arg, mp::utils::QuoteType quote_type)
QString find_autostart_target(const QString& subdir, const QString& autostart_filename)
{
const auto target_subpath = QDir{subdir}.filePath(autostart_filename);
const auto target_path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, target_subpath);
const auto target_path =
mp::StandardPaths::instance().locate(mp::StandardPaths::GenericDataLocation, target_subpath);

if (target_path.isEmpty())
{
QString detail{};
for (const auto& path : QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation))
for (const auto& path : mp::StandardPaths::instance().standardLocations(mp::StandardPaths::GenericDataLocation))
detail += QStringLiteral("\n ") + path + "/" + target_subpath;

throw mp::AutostartSetupException{fmt::format("could not locate the autostart file '{}'", autostart_filename),
Expand Down
2 changes: 2 additions & 0 deletions tests/CMakeLists.txt
Expand Up @@ -27,6 +27,7 @@ add_executable(multipass_tests
mischievous_url_downloader.cpp
mock_process_factory.cpp
mock_settings.cpp
mock_standard_paths.cpp
mock_sftp.cpp
mock_sftpserver.cpp
mock_ssh.cpp
Expand Down Expand Up @@ -54,6 +55,7 @@ add_executable(multipass_tests
test_petname.cpp
test_private_pass_provider.cpp
test_mock_settings.cpp
test_mock_standard_paths.cpp
test_simple_streams_index.cpp
test_simple_streams_manifest.cpp
test_singleton.cpp
Expand Down
5 changes: 4 additions & 1 deletion tests/main.cpp
@@ -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 @@ -16,6 +16,7 @@
*/

#include "mock_settings.h"
#include "mock_standard_paths.h"

#include <gtest/gtest.h>

Expand All @@ -31,6 +32,8 @@ int main(int argc, char* argv[])
QCoreApplication::setApplicationName("multipass_tests");

::testing::InitGoogleTest(&argc, argv);
mp::test::MockStandardPaths::mockit();
mp::test::MockSettings::mockit();

return RUN_ALL_TESTS();
}

1 comment on commit 8e085b5

@multipass-ci-bot
Copy link
Collaborator

@multipass-ci-bot multipass-ci-bot commented on 8e085b5 Apr 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.