Skip to content
Open
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
56 changes: 56 additions & 0 deletions src/framework/multiwindows/tests/mocks/multiwindowsprovidermock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited and others
*
* 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
* published by the Free Software Foundation.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once

#include <gmock/gmock.h>

#include "multiwindows/imultiwindowsprovider.h"

namespace muse::mi {
class MultiWindowsProviderMock : public IMultiWindowsProvider
{
public:
MOCK_METHOD(size_t, windowCount, (), (const, override));
MOCK_METHOD(bool, isFirstWindow, (), (const, override));
MOCK_METHOD(bool, isProjectAlreadyOpened, (const io::path_t&), (const, override));
MOCK_METHOD(void, activateWindowWithProject, (const io::path_t&), (override));
MOCK_METHOD(bool, isHasWindowWithoutProject, (), (const, override));
MOCK_METHOD(void, activateWindowWithoutProject, (const QStringList&), (override));
MOCK_METHOD(bool, openNewWindow, (const QStringList&), (override));
MOCK_METHOD(bool, isPreferencesAlreadyOpened, (), (const, override));
MOCK_METHOD(void, activateWindowWithOpenedPreferences, (), (const, override));
MOCK_METHOD(void, settingsBeginTransaction, (), (override));
MOCK_METHOD(void, settingsCommitTransaction, (), (override));
MOCK_METHOD(void, settingsRollbackTransaction, (), (override));
MOCK_METHOD(void, settingsReset, (), (override));
MOCK_METHOD(void, settingsSetValue, (const std::string&, const Val&), (override));
MOCK_METHOD(bool, lockResource, (const std::string&), (override));
MOCK_METHOD(bool, unlockResource, (const std::string&), (override));
MOCK_METHOD(void, notifyAboutResourceChanged, (const std::string&), (override));
MOCK_METHOD(async::Channel<std::string>, resourceChanged, (), (override));
MOCK_METHOD(void, notifyAboutWindowWasQuited, (), (override));
MOCK_METHOD(void, quitForAll, (), (override));
MOCK_METHOD(void, quitWindow, (const modularity::ContextPtr&), (override));
MOCK_METHOD(void, quitAllAndRestartLast, (), (override));
MOCK_METHOD(void, quitAllAndRunInstallation, (const io::path_t&), (override));
};
}
4 changes: 4 additions & 0 deletions src/framework/workspace/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,7 @@ target_sources(muse_workspace PRIVATE
if (MUSE_MODULE_WORKSPACE_QML)
add_subdirectory(qml/Muse/Workspace)
endif()

if (MUSE_MODULE_WORKSPACE_TESTS)
add_subdirectory(tests)
endif()
Comment on lines +57 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify declaration and usage of the workspace tests toggle.
rg -n --type=cmake -C2 '\bMUSE_MODULE_WORKSPACE_TESTS\b'

Repository: musescore/MuseScore

Length of output: 350


Add or declare the MUSE_MODULE_WORKSPACE_TESTS toggle before use.

The variable MUSE_MODULE_WORKSPACE_TESTS is checked at line 57 but is never declared or set anywhere in the codebase. In CMake, undefined variables evaluate to false in conditionals, which means the tests directory will never be included regardless of intent. Either remove this check if tests are always needed, or declare the option/variable (e.g., option(MUSE_MODULE_WORKSPACE_TESTS "Enable workspace tests" OFF)) in the parent CMakeLists.txt or cmake configuration.

44 changes: 44 additions & 0 deletions src/framework/workspace/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: GPL-3.0-only
# MuseScore-CLA-applies
#
# MuseScore
# Music Composition & Notation
#
# Copyright (C) 2026 MuseScore Limited and others
#
# 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
# published by the Free Software Foundation.
#
# 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 <https://www.gnu.org/licenses/>.

set(MODULE_TEST muse_workspace_tests)

set(MODULE_TEST_SRC
${CMAKE_CURRENT_LIST_DIR}/mocks/workspaceconfigurationmock.h
${MUSE_FRAMEWORK_SRC_PATH}/multiwindows/tests/mocks/multiwindowsprovidermock.h
${CMAKE_CURRENT_LIST_DIR}/environment.cpp
${CMAKE_CURRENT_LIST_DIR}/workspacemanager_tests.cpp
)

set(MODULE_TEST_LINK
muse::workspace
)

set(MODULE_TEST_INCLUDE
${MUSE_FRAMEWORK_SRC_PATH}/workspace
${MUSE_FRAMEWORK_SRC_PATH}/workspace/internal
${MUSE_FRAMEWORK_SRC_PATH}/multiwindows
)

set(MODULE_TEST_DEF
BUILTIN_WORKSPACES_DIR="${CMAKE_SOURCE_DIR}/share/workspaces"
)

include(SetupGTest)
40 changes: 40 additions & 0 deletions src/framework/workspace/tests/environment.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited and others
*
* 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
* published by the Free Software Foundation.
*
* 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 <https://www.gnu.org/licenses/>.
*/

#include "testing/environment.h"

#include "workspace/tests/mocks/workspaceconfigurationmock.h"

using namespace ::testing;

static muse::testing::SuiteEnvironment workspace_se
= muse::testing::SuiteEnvironment()
.setPreInit([](){
auto workspaceConfig = std::make_shared<::testing::NiceMock<muse::workspace::WorkspaceConfigurationMock> >();

ON_CALL(*workspaceConfig, defaultWorkspaceName())
.WillByDefault(Return("Default"));

muse::modularity::globalIoc()->registerExport<muse::workspace::IWorkspaceConfiguration>("utests", workspaceConfig);
}).setDeInit([](){
muse::modularity::globalIoc()->unregister<muse::workspace::IWorkspaceConfiguration>("utests");
});
40 changes: 40 additions & 0 deletions src/framework/workspace/tests/mocks/workspaceconfigurationmock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited and others
*
* 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
* published by the Free Software Foundation.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#pragma once

#include <gmock/gmock.h>

#include "workspace/iworkspaceconfiguration.h"

namespace muse::workspace {
class WorkspaceConfigurationMock : public IWorkspaceConfiguration
{
public:
MOCK_METHOD(io::paths_t, workspacePaths, (), (const, override));
MOCK_METHOD(io::paths_t, builtinWorkspacesFilePaths, (), (const, override));
MOCK_METHOD(io::path_t, userWorkspacesPath, (), (const, override));
MOCK_METHOD(std::string, defaultWorkspaceName, (), (const, override));
MOCK_METHOD(std::string, currentWorkspaceName, (), (const, override));
MOCK_METHOD(void, setCurrentWorkspaceName, (const std::string&), (override));
MOCK_METHOD(async::Channel<std::string>, currentWorkspaceNameChanged, (), (const, override));
};
}
171 changes: 171 additions & 0 deletions src/framework/workspace/tests/workspacemanager_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2026 MuseScore Limited and others
*
* 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
* published by the Free Software Foundation.
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include <gtest/gtest.h>
#include <gmock/gmock.h>

#include <QTemporaryDir>

#include "modularity/ioc.h"
#include "workspace/internal/workspacemanager.h"
#include "workspace/iworkspaceconfiguration.h"
#include "workspace/tests/mocks/workspaceconfigurationmock.h"
#include "multiwindows/tests/mocks/multiwindowsprovidermock.h"

using ::testing::Return;

using namespace muse;
using namespace muse::workspace;

static const std::string BUILTIN_WORKSPACE_DIR = BUILTIN_WORKSPACES_DIR;

namespace muse::workspace {
class Workspace_WorkspaceManagerTests : public ::testing::Test
{
public:
void SetUp() override
{
m_userWorkspacesDir = std::make_unique<QTemporaryDir>();
ASSERT_TRUE(m_userWorkspacesDir->isValid());

m_multiWindowsProvider = std::make_shared<::testing::NiceMock<mi::MultiWindowsProviderMock> >();
modularity::globalIoc()->registerExport<mi::IMultiWindowsProvider>("utests", m_multiWindowsProvider);

m_workspaceConfig = std::dynamic_pointer_cast<muse::workspace::WorkspaceConfigurationMock>(
modularity::globalIoc()->resolve<IWorkspaceConfiguration>("utests"));
ASSERT_NE(m_workspaceConfig, nullptr);

m_userWorkspacesPath = m_userWorkspacesDir->path().toStdString();
}

void TearDown() override
{
m_manager.reset();
modularity::globalIoc()->unregister<mi::IMultiWindowsProvider>("utests");
}
Comment on lines +59 to +63
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Move manager deinitialization into TearDown() for guaranteed cleanup.

Right now cleanup depends on reaching the end of each test body. A fatal assertion can skip deinit() and leave state behind for subsequent tests.

Proposed fix
 void TearDown() override
 {
-    m_manager.reset();
+    if (m_manager) {
+        m_manager->deinit();
+        m_manager.reset();
+    }
     modularity::globalIoc()->unregister<mi::IMultiWindowsProvider>("utests");
 }
@@
-    m_manager->deinit();
 }
@@
-    m_manager->deinit();
 }
@@
-    m_manager->deinit();
 }

Also applies to: 118-119, 143-144, 169-170


void setupBuiltinPaths(const std::vector<std::string>& filenames)
{
io::paths_t paths;
for (const auto& name : filenames) {
paths.push_back(io::path_t(BUILTIN_WORKSPACE_DIR + "/" + name));
}

ON_CALL(*m_workspaceConfig, builtinWorkspacesFilePaths())
.WillByDefault(Return(paths));

ON_CALL(*m_workspaceConfig, userWorkspacesPath())
.WillByDefault(Return(io::path_t(m_userWorkspacesPath)));
}

void initManager()
{
m_manager = std::make_unique<WorkspaceManager>(modularity::globalCtx());
m_manager->init();
}

protected:
std::unique_ptr<QTemporaryDir> m_userWorkspacesDir;
std::string m_userWorkspacesPath;

std::shared_ptr<::testing::NiceMock<mi::MultiWindowsProviderMock> > m_multiWindowsProvider;
std::shared_ptr<muse::workspace::WorkspaceConfigurationMock> m_workspaceConfig;

std::unique_ptr<WorkspaceManager> m_manager;
};

TEST_F(Workspace_WorkspaceManagerTests, BuiltinWorkspaceLoadsOnInit)
{
//! [GIVEN] Builtin workspace file exists
setupBuiltinPaths({ "Default.mws" });

//! [GIVEN] Current workspace is "Default"
ON_CALL(*m_workspaceConfig, currentWorkspaceName())
.WillByDefault(Return("Default"));

//! [WHEN] WorkspaceManager is initialized
initManager();

//! [THEN] Default workspace is loaded
EXPECT_NE(m_manager->defaultWorkspace(), nullptr);
EXPECT_EQ(m_manager->defaultWorkspace()->name(), "Default");

//! [THEN] Current workspace matches default
EXPECT_NE(m_manager->currentWorkspace(), nullptr);
EXPECT_EQ(m_manager->currentWorkspace()->name(), "Default");

//! [THEN] Builtin workspace is available
EXPECT_EQ(m_manager->workspaces().size(), 1);

m_manager->deinit();
}

TEST_F(Workspace_WorkspaceManagerTests, FallbackToDefaultOnMissingCurrentWorkspace)
{
//! [GIVEN] Builtin workspace file exists
setupBuiltinPaths({ "Default.mws" });

//! [GIVEN] Current workspace name references a workspace that no longer exists
ON_CALL(*m_workspaceConfig, currentWorkspaceName())
.WillByDefault(Return("MyDeletedWorkspace"));

//! [GIVEN] Manager will correct the setting to default
EXPECT_CALL(*m_workspaceConfig, setCurrentWorkspaceName("Default"))
.Times(1);

//! [WHEN] WorkspaceManager is initialized
initManager();

//! [THEN] Falls back to default workspace
EXPECT_NE(m_manager->currentWorkspace(), nullptr);
EXPECT_EQ(m_manager->currentWorkspace()->name(), "Default");
EXPECT_NE(m_manager->defaultWorkspace(), nullptr);
EXPECT_EQ(m_manager->defaultWorkspace()->name(), "Default");

m_manager->deinit();
}

TEST_F(Workspace_WorkspaceManagerTests, EmptyUserDirUsesBuiltinWorkspaces)
{
//! [GIVEN] Builtin workspace file exists, user dir is empty
setupBuiltinPaths({ "Default.mws" });

ON_CALL(*m_workspaceConfig, currentWorkspaceName())
.WillByDefault(Return("Default"));

//! [WHEN] WorkspaceManager is initialized
initManager();

//! [THEN] Workspaces are loaded from builtins only
EXPECT_EQ(m_manager->workspaces().size(), 1);

//! [THEN] Current workspace is loaded and usable
auto current = m_manager->currentWorkspace();
ASSERT_NE(current, nullptr);
EXPECT_EQ(current->name(), "Default");

//! [THEN] Reading workspace data does not crash
auto data = current->rawData("ui_settings");
EXPECT_TRUE(data.ret);

m_manager->deinit();
}
}
Loading