diff --git a/.github/workflows/windows-macos.yml b/.github/workflows/windows-macos.yml index a004eb8bdc..846afe48cb 100644 --- a/.github/workflows/windows-macos.yml +++ b/.github/workflows/windows-macos.yml @@ -245,9 +245,9 @@ jobs: - name: Install Qt uses: jurplel/install-qt-action@v4 with: - version: 6.2.4 + version: 6.10.0 setup-python: false - aqtversion: '==3.1.*' + aqtversion: '==3.3.*' py7zrversion: '==0.20.*' timeout-minutes: 20 diff --git a/BUILD.macOS.md b/BUILD.macOS.md index f08253091e..fe45c98f5a 100644 --- a/BUILD.macOS.md +++ b/BUILD.macOS.md @@ -18,14 +18,14 @@ Avoid the version of cmake supplied, we need a newer one (see later). #### Option 1: Using Qt official sources -Install the latest stable version of Qt6 LTS (6.2.4 at the moment): . +Install the latest stable version of Qt6 LTS (6.10.0 at the moment): . If it tells you that XCode 5.0.0 needs to be installed, go to XCode > Preferences > Locations and make a selection in the _Command Line Tools_ box. Add Qt6 to your PATH environment variable, adding to your `.bash_profile` file the following line: - export PATH=$PATH:~/Qt/6.2.4/clang_64/bin + export PATH=$PATH:~/Qt/6.10.0/clang_64/bin Adjust accordingly if you customized the Qt install directory. @@ -50,7 +50,7 @@ Building To build with official Qt sources do: - cmake -Bbuild -H. -GNinja -DCMAKE_PREFIX_PATH=~/Qt/6.2.4/clang_64 + cmake -Bbuild -H. -GNinja -DCMAKE_PREFIX_PATH=~/Qt/6.10.0/clang_64 Alternatively if using Qt6 from Homebrew, do diff --git a/BUILD.windows.md b/BUILD.windows.md index dc7d699d6b..e721fccb9d 100644 --- a/BUILD.windows.md +++ b/BUILD.windows.md @@ -54,11 +54,11 @@ choco install aqt -yfd Then specify the following options in the installation command: ```[pwsh] -aqt install-qt windows desktop 6.2.4 win64_msvc2019_64 -O C:/Qt +aqt install-qt windows desktop 6.10.0 win64_msvc2022_64 -O C:/Qt ``` -Alternatively, download the [qtbase archive](https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_624/qt.qt6.624.win64_msvc2019_64/6.2.4-0-202203140926qtbase-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z) -and extract it to `C:\Qt` (so it ends up in `C:\Qt\6.2.4`). +Alternatively, download the [qtbase archive](https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt6_6100/qt6_6100/qt.qt6.6100.win64_msvc2022_64/6.10.0-0-202510021201qtbase-Windows-Windows_11_24H2-MSVC2022-Windows-Windows_11_24H2-X86_64.7z) +and extract it to `C:\Qt\6.10.0`. ### Path setup @@ -67,7 +67,7 @@ You'll have to manually add CMake and Qt to your account's PATH variable. Search for "Edit environment variables for your account" then edit your Path variable. Add the following: - `C:\Program Files\CMake\bin` -- `C:\Qt\6.2.4\msvc2019_64\bin` +- `C:\Qt\6.10.0\bin` ### Console setup diff --git a/README.md b/README.md index bc000ecbf9..02bc00fe01 100644 --- a/README.md +++ b/README.md @@ -180,7 +180,7 @@ Please follow the platform-specific build instructions in the files below: ### Generic build tips **Qt version compatibility** -Multipass is tested with **Qt 6.2.4**. Newer patch versions along the 6.2 track (e.g. 6.2.8) should be fine. Newer minor versions may work, but they may cause compatibility issues. +Multipass is tested with **Qt 6.10.0**. Newer patch versions along the 6.10 track (e.g. 6.10.4) should be fine. Newer minor versions may work, but they may cause compatibility issues. **ARM64 and cross-compilation** If you're using Apple Silicon (arm64) or cross-compiling, ensure that your `PATH` and `CMAKE_PREFIX_PATH` environment variables point to the correct Qt installation for your system architecture. diff --git a/include/multipass/utils.h b/include/multipass/utils.h index b8586f4ecf..68c0a93854 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -71,6 +71,13 @@ bool is_dir(const std::string& path); QString backend_directory_path(const Path& path, const QString& subdirectory); std::string contents_of(const multipass::Path& file_path); +// path normalization: returns the lexically-normal form of the path with any trailing directory +// separator removed. The string overloads additionally convert directory separators to generic +// form ("/"). +std::filesystem::path normalize_path(const std::filesystem::path& path); +std::string normalize_path(const std::string& path); +QString normalize_path(const QString& path); + // filesystem mount helpers void make_target_dir(SSHSession& session, const std::string& root, diff --git a/src/cert/client_cert_store.cpp b/src/cert/client_cert_store.cpp index 2aa9b3699e..ce02a17d46 100644 --- a/src/cert/client_cert_store.cpp +++ b/src/cert/client_cert_store.cpp @@ -43,11 +43,8 @@ auto load_certs_from_file(const QDir& cert_dir) QFile cert_file{path}; - if (cert_file.exists()) - { - cert_file.open(QFile::ReadOnly); + if (cert_file.exists() && cert_file.open(QFile::ReadOnly)) certs = QSslCertificate::fromDevice(&cert_file); - } return certs; } diff --git a/src/client/cli/cmd/exec.cpp b/src/client/cli/cmd/exec.cpp index 83629cacbe..fe6c2db0fc 100644 --- a/src/client/cli/cmd/exec.cpp +++ b/src/client/cli/cmd/exec.cpp @@ -20,8 +20,10 @@ #include #include +#include namespace mp = multipass; +namespace mpu = multipass::utils; namespace cmd = multipass::cmd; namespace @@ -75,14 +77,14 @@ mp::ReturnCode cmd::Exec::run(mp::ArgParser* parser) (!parser->executeAlias() && !parser->isSet(no_dir_mapping_option))) { // The host directory on which the user is executing the command. - QString clean_exec_dir = QDir::cleanPath(QDir::current().canonicalPath()); + QString clean_exec_dir = mpu::normalize_path(QDir::current().canonicalPath()); QStringList split_exec_dir = clean_exec_dir.split('/'); auto on_info_success = [&work_dir, &split_exec_dir](mp::InfoReply& reply) { for (const auto& mount : reply.details(0).mount_info().mount_paths()) { auto source_dir = QDir(QString::fromStdString(mount.source_path())); - auto clean_source_dir = QDir::cleanPath(source_dir.absolutePath()); + auto clean_source_dir = mpu::normalize_path(source_dir.absolutePath()); QStringList split_source_dir = clean_source_dir.split('/'); // If the directory is mounted, we need to `cd` to it in the instance before diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index ffce19331e..1dd28c86a4 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -2500,8 +2500,7 @@ try for (const auto& path_entry : request->target_paths()) { const auto& name = path_entry.instance_name(); - const auto target_path = - QDir::cleanPath(QString::fromStdString(path_entry.target_path())).toStdString(); + const auto target_path = mpu::normalize_path(path_entry.target_path()); if (operative_instances.find(name) == operative_instances.end()) { diff --git a/src/network/url_downloader.cpp b/src/network/url_downloader.cpp index 22ef8ada9c..aa511f8a01 100644 --- a/src/network/url_downloader.cpp +++ b/src/network/url_downloader.cpp @@ -249,7 +249,9 @@ void mp::URLDownloader::download_to(const QUrl& url, auto manager{MP_NETMGRFACTORY.make_network_manager(cache_dir_path)}; QFile file{file_name}; - file.open(QIODevice::ReadWrite | QIODevice::Truncate); + if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) + throw std::runtime_error( + fmt::format("unable to write to file \"{}\"", file_name.toStdString())); auto progress_monitor = [this, &abort_download, &monitor, download_type, size]( QNetworkReply* reply, diff --git a/src/platform/backends/qemu/linux/dnsmasq_server.cpp b/src/platform/backends/qemu/linux/dnsmasq_server.cpp index b65659ae2f..4dbd7ab5ed 100644 --- a/src/platform/backends/qemu/linux/dnsmasq_server.cpp +++ b/src/platform/backends/qemu/linux/dnsmasq_server.cpp @@ -55,13 +55,16 @@ mp::DNSMasqServer::DNSMasqServer(const Path& data_dir, subnet{subnet}, conf_file{QDir(data_dir).absoluteFilePath("dnsmasq-XXXXXX.conf")} { - conf_file.open(); + if (!conf_file.open()) + throw std::runtime_error("unable to create temporary dnsmasq conf file"); conf_file.close(); QFile dnsmasq_hosts(QDir(data_dir).filePath("dnsmasq.hosts")); if (!dnsmasq_hosts.exists()) { - dnsmasq_hosts.open(QIODevice::WriteOnly); + if (!dnsmasq_hosts.open(QIODevice::WriteOnly)) + throw std::runtime_error( + fmt::format("unable to create file {}", dnsmasq_hosts.filesystemFileName())); } dnsmasq_cmd = make_dnsmasq_process(data_dir, bridge_name, subnet, conf_file.fileName()); diff --git a/src/utils/json_utils.cpp b/src/utils/json_utils.cpp index 154e15fa7b..d518165d8b 100644 --- a/src/utils/json_utils.cpp +++ b/src/utils/json_utils.cpp @@ -59,7 +59,7 @@ void mp::JsonUtils::write_json(const QJsonObject& root, QString file_name) const // Interprocess lock file to ensure that we can synchronize the request from // both the daemon and the client. - QLockFile lock(fi.filePath() + u".lock"_qs); + QLockFile lock(fi.filePath() + ".lock"); // Make the lock file stale after a while to avoid deadlocking // on process crashes, etc. diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 36fffd19bf..9bba4094d6 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +162,25 @@ QDir mp::utils::base_dir(const QString& path) return info.absoluteDir(); } +std::filesystem::path mp::utils::normalize_path(const std::filesystem::path& path) +{ + auto result = path.lexically_normal(); + // Remove the trailing slash if present. + if (!result.has_filename()) + result = result.parent_path(); + return result; +} + +std::string mp::utils::normalize_path(const std::string& path) +{ + return normalize_path(std::filesystem::path{path}).generic_string(); +} + +QString mp::utils::normalize_path(const QString& path) +{ + return QString::fromStdString(normalize_path(path.toStdString())); +} + bool mp::utils::valid_hostname(const std::string& name_string) { QRegularExpression matcher{ @@ -432,7 +452,9 @@ void mp::utils::check_and_create_config_file(const QString& config_file_path) MP_UTILS.make_dir( {}, QFileInfo{config_file_path}.dir().path()); // make sure parent dir is there - config_file.open(QIODevice::WriteOnly); + if (!config_file.open(QIODevice::WriteOnly)) + throw std::runtime_error( + fmt::format("Unable to create config file \"{}\"", config_file_path.toStdString())); } } @@ -604,22 +626,33 @@ bool mp::Utils::is_ipv4_valid(const std::string& ipv4) const mp::Path mp::Utils::default_mount_target(const Path& source) const { - return source.isEmpty() - ? "" - : QDir{QDir::cleanPath(source)}.dirName().prepend(QString{home_in_instance} + '/'); + // The GUI calls this function on every update of the host mount directory, so we need to be + // pretty lenient with what we accept for now. In the future, the GUI should be able to handle + // errors from this function as an indicator that the host mount directory is invalid. + if (source == "") + return ""; + + auto path = mp::utils::normalize_path(std::filesystem::absolute(source.toStdString())); + if (!path.has_filename()) + return ""; + auto mount_target = home_in_instance / path.filename(); + return QString::fromStdString(mount_target.generic_string()); } QString mp::Utils::normalize_mount_target(QString target_mount_path) const { - if (QDir::isRelativePath(target_mount_path)) // rely on Qt to understand Linux paths on Windows - target_mount_path.prepend('/').prepend(home_in_instance); // QString::prepend is fast + std::filesystem::path target_mount = target_mount_path.toStdString(); + if (target_mount.is_relative()) + target_mount = home_in_instance / target_mount; - return QDir::cleanPath(target_mount_path); + target_mount = mp::utils::normalize_path(target_mount); + return QString::fromStdString(target_mount.generic_string()); } bool mp::Utils::invalid_target_path(const QString& target_path) const { - assert(target_path == QDir::cleanPath(target_path) && "target_path must be normalized"); + assert(target_path == mp::utils::normalize_path(target_path) && + "target_path must be normalized"); static QRegularExpression matcher{ QRegularExpression::anchoredPattern("/+|/+(dev|proc|sys)(/.*)*|/+home(/*)(/ubuntu/*)*")}; diff --git a/tests/file_operations.cpp b/tests/file_operations.cpp index 501826db6a..fbcb809a6c 100644 --- a/tests/file_operations.cpp +++ b/tests/file_operations.cpp @@ -34,12 +34,11 @@ namespace mpt = multipass::test; QByteArray mpt::load(QString path) { QFile file(path); - if (file.exists()) - { - file.open(QIODevice::ReadOnly); - return file.readAll(); - } - throw std::invalid_argument(path.toStdString() + " does not exist"); + if (!file.exists()) + throw std::invalid_argument(path.toStdString() + " does not exist"); + if (!file.open(QIODevice::ReadOnly)) + throw std::runtime_error("failed to open " + path.toStdString()); + return file.readAll(); } QByteArray mpt::load_test_file(const char* file_name) diff --git a/tests/linux/test_platform_linux.cpp b/tests/linux/test_platform_linux.cpp index c5242340a8..45ed787ed7 100644 --- a/tests/linux/test_platform_linux.cpp +++ b/tests/linux/test_platform_linux.cpp @@ -531,7 +531,7 @@ TEST_F(PlatformLinux, createAliasScriptWorksUnconfined) mp::AliasDefinition{"instance", "command", "map"})); QFile checked_script(tmp_dir.path() + "/bin/alias_name"); - checked_script.open(QFile::ReadOnly); + ASSERT_TRUE(checked_script.open(QFile::ReadOnly)); EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n"); EXPECT_EQ(checked_script.readLine().toStdString(), "\n"); @@ -559,7 +559,7 @@ TEST_F(PlatformLinux, createAliasScriptWorksConfined) mp::AliasDefinition{"instance", "command", "map"})); QFile checked_script(tmp_dir.path() + "/bin/alias_name"); - checked_script.open(QFile::ReadOnly); + ASSERT_TRUE(checked_script.open(QFile::ReadOnly)); EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n"); EXPECT_EQ(checked_script.readLine().toStdString(), "\n"); diff --git a/tests/macos/test_platform_osx.cpp b/tests/macos/test_platform_osx.cpp index 50ca4764cf..9f9ce6a0da 100644 --- a/tests/macos/test_platform_osx.cpp +++ b/tests/macos/test_platform_osx.cpp @@ -312,7 +312,7 @@ TEST(PlatformOSX, createAliasScriptWorks) MP_PLATFORM.create_alias_script("alias_name", mp::AliasDefinition{"instance", "command"})); QFile checked_script(tmp_dir.path() + "/bin/alias_name"); - checked_script.open(QFile::ReadOnly); + ASSERT_TRUE(checked_script.open(QFile::ReadOnly)); EXPECT_EQ(checked_script.readLine().toStdString(), "#!/bin/sh\n"); EXPECT_EQ(checked_script.readLine().toStdString(), "\n"); diff --git a/tests/qemu/test_qemu_vm_process_spec.cpp b/tests/qemu/test_qemu_vm_process_spec.cpp index 1604e7e8a0..36996f6c29 100644 --- a/tests/qemu/test_qemu_vm_process_spec.cpp +++ b/tests/qemu/test_qemu_vm_process_spec.cpp @@ -18,6 +18,7 @@ #include "tests/common.h" #include "tests/mock_environment_helpers.h" +#include #include #include @@ -27,6 +28,7 @@ namespace mp = multipass; namespace mpt = multipass::test; +namespace mpu = multipass::utils; using namespace testing; struct TestQemuVMProcessSpec : public Test @@ -130,23 +132,24 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileIncludesFileMountPerms) { mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains("path/to/source/ rw")); - EXPECT_TRUE(spec.apparmor_profile().contains("path/to/source/** rwlk")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), HasSubstr("path/to/source/ rw")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), HasSubstr("path/to/source/** rwlk")); } TEST_F(TestQemuVMProcessSpec, apparmorProfileHasCorrectName) { mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains("profile multipass.vm_name.qemu-system-")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr("profile multipass.vm_name.qemu-system-")); } TEST_F(TestQemuVMProcessSpec, apparmorProfileIncludesDiskImages) { mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains("/path/to/image rwk,")); - EXPECT_TRUE(spec.apparmor_profile().contains("/path/to/cloud_init.iso rk,")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), HasSubstr("/path/to/image rwk,")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), HasSubstr("/path/to/cloud_init.iso rk,")); } TEST_F(TestQemuVMProcessSpec, apparmorProfileIdentifier) @@ -165,11 +168,12 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileRunningAsSnapCorrect) mpt::SetEnvScope e2("SNAP_NAME", snap_name); mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE( - spec.apparmor_profile().contains("signal (receive) peer=snap.multipass.multipassd")); - EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1/qemu/* r,").arg(snap_dir.path()))); - EXPECT_TRUE( - spec.apparmor_profile().contains(QString("%1/usr/bin/qemu-system-").arg(snap_dir.path()))); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr("signal (receive) peer=snap.multipass.multipassd")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(QString("%1/qemu/* r,").arg(snap_dir.path()).toStdString())); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(QString("%1/usr/bin/qemu-system-").arg(snap_dir.path()).toStdString())); } TEST_F(TestQemuVMProcessSpec, apparmorProfileRunningAsSymlinkedSnapCorrect) @@ -184,9 +188,10 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileRunningAsSymlinkedSnapCorrect) mpt::SetEnvScope e2("SNAP_NAME", snap_name); mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains(QString("%1/qemu/* r,").arg(snap_dir.path()))); - EXPECT_TRUE( - spec.apparmor_profile().contains(QString("%1/usr/bin/qemu-system-").arg(snap_dir.path()))); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(QString("%1/qemu/* r,").arg(snap_dir.path()).toStdString())); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(QString("%1/usr/bin/qemu-system-").arg(snap_dir.path()).toStdString())); } TEST_F(TestQemuVMProcessSpec, apparmorProfileNotRunningAsSnapCorrect) @@ -197,10 +202,12 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileNotRunningAsSnapCorrect) mpt::SetEnvScope e2("SNAP_NAME", snap_name); mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains("signal (receive) peer=unconfined")); - EXPECT_TRUE( - spec.apparmor_profile().contains("/usr{,/local}/share/{seabios,ovmf,qemu,qemu-efi}/* r,")); - EXPECT_TRUE(spec.apparmor_profile().contains(" /usr/bin/qemu-system-")); // space wanted + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr("signal (receive) peer=unconfined")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr("/usr{,/local}/share/{seabios,ovmf,qemu,qemu-efi}/* r,")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(" /usr/bin/qemu-system-")); // space wanted } TEST_F(TestQemuVMProcessSpec, apparmorProfileLetsBridgeHelperRunInSnap) @@ -212,8 +219,8 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileLetsBridgeHelperRunInSnap) mpt::SetEnvScope e2("SNAP_NAME", snap_name); mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE( - spec.apparmor_profile().contains(QString(" %1/bin/bridge_helper").arg(snap_dir.path()))); + EXPECT_THAT(spec.apparmor_profile().toStdString(), + HasSubstr(QString(" %1/bin/bridge_helper").arg(mpu::snap_dir()).toStdString())); } TEST_F(TestQemuVMProcessSpec, apparmorProfileLetsBridgeHelperRunOutsideSnap) @@ -224,5 +231,5 @@ TEST_F(TestQemuVMProcessSpec, apparmorProfileLetsBridgeHelperRunOutsideSnap) mpt::SetEnvScope e2("SNAP_NAME", snap_name); mp::QemuVMProcessSpec spec(desc, platform_args, mount_args, std::nullopt); - EXPECT_TRUE(spec.apparmor_profile().contains(" /bin/bridge_helper")); + EXPECT_THAT(spec.apparmor_profile().toStdString(), HasSubstr(" /bin/bridge_helper")); } diff --git a/tests/test_alias_dict.cpp b/tests/test_alias_dict.cpp index 8d1f96ffca..9ecbe8a901 100644 --- a/tests/test_alias_dict.cpp +++ b/tests/test_alias_dict.cpp @@ -51,8 +51,9 @@ struct AliasDictionary : public FakeAliasConfig, public Test TEST_F(AliasDictionary, worksWithEmptyFile) { QFile db(QString::fromStdString(db_filename())); + MP_UTILS.make_dir(QFileInfo(db).dir()); - db.open(QIODevice::ReadWrite); // Create the database file. + ASSERT_TRUE(db.open(QIODevice::ReadWrite)); // Create the database file. std::stringstream trash_stream; mpt::StubTerminal trash_term(trash_stream, trash_stream, trash_stream); diff --git a/tests/test_cli_client.cpp b/tests/test_cli_client.cpp index 55c250cb18..d3b3f347b5 100644 --- a/tests/test_cli_client.cpp +++ b/tests/test_cli_client.cpp @@ -1244,7 +1244,7 @@ TEST_F(Client, DISABLE_ON_MACOS(launchCmdCustomImageHttpOk)) TEST_F(Client, launchCmdCloudinitOptionWithValidFileIsOk) { QTemporaryFile tmpfile; // file is auto-deleted when this goes out of scope - tmpfile.open(); + ASSERT_TRUE(tmpfile.open()); tmpfile.write("password: passw0rd"); // need some YAML tmpfile.close(); EXPECT_CALL(mock_daemon, launch(_, _)); diff --git a/tests/test_image_vault_utils.cpp b/tests/test_image_vault_utils.cpp index 659c9f3b3e..d249d79863 100644 --- a/tests/test_image_vault_utils.cpp +++ b/tests/test_image_vault_utils.cpp @@ -96,7 +96,7 @@ TEST_F(TestImageVaultUtils, computeHashComputesSha256) QByteArray data = ":)"; QBuffer buffer{&data}; - buffer.open(QIODevice::ReadOnly); + ASSERT_TRUE(buffer.open(QIODevice::ReadOnly)); auto hash = MP_IMAGE_VAULT_UTILS.compute_hash(buffer); EXPECT_EQ(hash, "54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c"); diff --git a/tests/test_new_release_monitor.cpp b/tests/test_new_release_monitor.cpp index c453c6d0ed..0f4e4967e8 100644 --- a/tests/test_new_release_monitor.cpp +++ b/tests/test_new_release_monitor.cpp @@ -47,7 +47,8 @@ class StubUpdateJson public: StubUpdateJson(QString version, QString url) { - json_file.open(); + if (!json_file.open()) + throw std::runtime_error("test failed to create temporary file"); json_file.write(json_template.arg(url).arg(version).toUtf8()); json_file.close(); } diff --git a/tests/test_url_downloader.cpp b/tests/test_url_downloader.cpp index 21ffd8fb75..dd726ceabb 100644 --- a/tests/test_url_downloader.cpp +++ b/tests/test_url_downloader.cpp @@ -232,7 +232,7 @@ TEST_F(URLDownloader, fileDownloadNoErrorHasExpectedResults) QFile test_file{download_file}; ASSERT_TRUE(test_file.exists()); - test_file.open(QIODevice::ReadOnly); + ASSERT_TRUE(test_file.open(QIODevice::ReadOnly)); auto file_data = test_file.readAll(); EXPECT_EQ(file_data, test_data); } @@ -291,7 +291,7 @@ TEST_F(URLDownloader, fileDownloadErrorTriesCache) QFile test_file{download_file}; ASSERT_TRUE(test_file.exists()); - test_file.open(QIODevice::ReadOnly); + ASSERT_TRUE(test_file.open(QIODevice::ReadOnly)); auto file_data = test_file.readAll(); EXPECT_EQ(file_data, test_data); } diff --git a/tests/test_utils.cpp b/tests/test_utils.cpp index ab42a3788e..c642bb85db 100644 --- a/tests/test_utils.cpp +++ b/tests/test_utils.cpp @@ -53,7 +53,7 @@ std::string file_contents{"line 1 of file contents\nline 2\n"}; void check_file_contents(QFile& checked_file, const std::string& checked_contents) { - checked_file.open(QIODevice::ReadOnly | QIODevice::Text); + ASSERT_TRUE(checked_file.open(QIODevice::ReadOnly | QIODevice::Text)); QString actual_contents; @@ -642,9 +642,40 @@ TEST(Utils, checkFilesystemBytesAvailableReturnsNonNegative) EXPECT_GE(bytes_available, 0); } -using NormalizeMountTargetParam = std::tuple; +using MountTargetParam = std::tuple; -class NormalizeMountTargetTest : public TestWithParam +class DefaultMountTargetTest : public TestWithParam +{ +}; + +TEST_P(DefaultMountTargetTest, mountTargetsDefaultCorrectly) +{ + const auto& [input, expected_output] = GetParam(); + + auto result = MP_UTILS.default_mount_target(QString::fromStdString(input)); + EXPECT_EQ(result.toStdString(), expected_output); +} + +std::vector default_mount_target_values = { + std::make_tuple("", ""), + std::make_tuple("/", ""), + std::make_tuple("/.", ""), + std::make_tuple("Documents", "/home/ubuntu/Documents"), + std::make_tuple("Documents/", "/home/ubuntu/Documents"), + std::make_tuple("Documents/.", "/home/ubuntu/Documents"), + std::make_tuple("/home/user/Desktop", "/home/ubuntu/Desktop"), + std::make_tuple("/home/user/Desktop/", "/home/ubuntu/Desktop"), + std::make_tuple("/folder/subfolder/..", "/home/ubuntu/folder"), +#ifdef MULTIPASS_PLATFORM_WINDOWS + std::make_tuple(R"(Documents\)", "/home/ubuntu/Documents"), + std::make_tuple(R"(C:\Users\user\Documents)", "/home/ubuntu/Documents"), + std::make_tuple(R"(C:\Users\user\Documents\)", "/home/ubuntu/Documents"), +#endif +}; + +INSTANTIATE_TEST_SUITE_P(Utils, DefaultMountTargetTest, ValuesIn(default_mount_target_values)); + +class NormalizeMountTargetTest : public TestWithParam { }; @@ -656,19 +687,27 @@ TEST_P(NormalizeMountTargetTest, mountTargetsNormalizeCorrectly) EXPECT_EQ(result.toStdString(), expected_output); } -INSTANTIATE_TEST_SUITE_P( - Utils, - NormalizeMountTargetTest, - Values(std::make_tuple("Documents", "/home/ubuntu/Documents"), - std::make_tuple("./folder", "/home/ubuntu/folder"), - std::make_tuple("../folder", "/home/folder"), - std::make_tuple("../..//folder", "/folder"), - std::make_tuple("/usr/local/bin//.././/.//bin/././", "/usr/local/bin"), - std::make_tuple("folder//subfolder", "/home/ubuntu/folder/subfolder"), - std::make_tuple("./Documents/../Downloads", "/home/ubuntu/Downloads"), - std::make_tuple("", "/home/ubuntu"), - std::make_tuple(".", "/home/ubuntu"), - std::make_tuple("..", "/home"), - std::make_tuple("folder/./subfolder", "/home/ubuntu/folder/subfolder"), - std::make_tuple("folder/../other-folder", "/home/ubuntu/other-folder"), - std::make_tuple("//", "/"))); +std::vector normalize_mount_target_values = { + std::make_tuple("Documents", "/home/ubuntu/Documents"), + std::make_tuple("", "/home/ubuntu"), + std::make_tuple(".", "/home/ubuntu"), + std::make_tuple("..", "/home"), + std::make_tuple("./", "/home/ubuntu"), + std::make_tuple("folder/", "/home/ubuntu/folder"), +#ifdef MULTIPASS_PLATFORM_WINDOWS + std::make_tuple(R"(.\)", "/home/ubuntu"), + std::make_tuple(R"(folder\)", "/home/ubuntu/folder"), + std::make_tuple(R"(folder\subfolder)", "/home/ubuntu/folder/subfolder"), +#endif + std::make_tuple("./folder", "/home/ubuntu/folder"), + std::make_tuple("../folder", "/home/folder"), + std::make_tuple("../..//folder", "/folder"), + std::make_tuple("/usr/local/bin//.././/.//bin/././", "/usr/local/bin"), + std::make_tuple("folder//subfolder", "/home/ubuntu/folder/subfolder"), + std::make_tuple("./Documents/../Downloads", "/home/ubuntu/Downloads"), + std::make_tuple("folder/./subfolder", "/home/ubuntu/folder/subfolder"), + std::make_tuple("folder/../other-folder", "/home/ubuntu/other-folder"), + std::make_tuple("//", "/"), +}; + +INSTANTIATE_TEST_SUITE_P(Utils, NormalizeMountTargetTest, ValuesIn(normalize_mount_target_values)); diff --git a/tests/windows/test_platform_win.cpp b/tests/windows/test_platform_win.cpp index be5b8b8bc0..e90b530762 100644 --- a/tests/windows/test_platform_win.cpp +++ b/tests/windows/test_platform_win.cpp @@ -615,7 +615,7 @@ TEST(PlatformWin, createAliasScriptWorks) MP_PLATFORM.create_alias_script("alias_name", mp::AliasDefinition{"instance", "command"})); QFile checked_script(tmp_dir.path() + "/AppData/local/multipass/bin/alias_name.bat"); - checked_script.open(QFile::ReadOnly); + ASSERT_TRUE(checked_script.open(QFile::ReadOnly)); std::string script_line = checked_script.readLine().toStdString(); EXPECT_THAT(script_line, HasSubstr("@"));