// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "projectmanager.h"

#include "buildconfiguration.h"
#include "editorconfiguration.h"
#include "project.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
#include "projectmanager.h"
#include "projectnodes.h"
#include "target.h"

#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/foldernavigationwidget.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
#include <coreplugin/imode.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/session.h>

#include <texteditor/texteditor.h>

#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/mimeutils.h>
#include <utils/persistentsettings.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>

#include <QDebug>
#include <QMessageBox>
#include <QPushButton>

#ifdef WITH_TESTS
#include "projectexplorer_test.h"

#include <QTemporaryFile>
#include <QTest>
#include <vector>
#endif

using namespace Core;
using namespace Utils;
using namespace ProjectExplorer::Internal;

namespace ProjectExplorer {

class ProjectManagerPrivate
{
public:
    void loadSession();
    void saveSession();
    void restoreDependencies();
    void restoreStartupProject();
    void restoreProjects(const FilePaths &fileList);
    void askUserAboutFailedProjects();

    bool recursiveDependencyCheck(const FilePath &newDep, const FilePath &checkDep) const;
    FilePaths dependencies(const FilePath &proName) const;
    FilePaths dependenciesOrder() const;
    void dependencies(const FilePath &proName, FilePaths &result) const;

    static QString windowTitleAddition(const FilePath &filePath);
    static QString sessionTitle(const FilePath &filePath);

    bool hasProjects() const { return !m_projects.isEmpty(); }

    bool m_casadeSetActive = false;
    bool m_deployProjectDependencies = false;

    Project *m_startupProject = nullptr;
    QList<Project *> m_projects;
    FilePaths m_failedProjects;
    QMap<FilePath, FilePaths> m_depMap;

private:
    static QString locationInProject(const FilePath &filePath);
};

static ProjectManager *m_instance = nullptr;
static ProjectManagerPrivate *d = nullptr;

static QString projectFolderId(Project *pro)
{
    return pro->projectFilePath().toUrlishString();
}

const int PROJECT_SORT_VALUE = 100;

ProjectManager::ProjectManager()
{
    m_instance = this;
    d = new ProjectManagerPrivate;

    connect(EditorManager::instance(), &EditorManager::editorCreated,
            this, &ProjectManager::configureEditor);
    connect(this, &ProjectManager::projectAdded,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(this, &ProjectManager::projectRemoved,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(this, &ProjectManager::projectDisplayNameChanged,
            EditorManager::instance(), &EditorManager::updateWindowTitles);
    connect(this, &ProjectManager::startupProjectChanged, this, [this] {
        emit activeBuildConfigurationChanged(activeBuildConfigForActiveProject());
    });

    EditorManager::setWindowTitleAdditionHandler(&ProjectManagerPrivate::windowTitleAddition);
    EditorManager::setSessionTitleHandler(&ProjectManagerPrivate::sessionTitle);

    connect(SessionManager::instance(), &SessionManager::aboutToLoadSession, this, [] {
        d->loadSession();
    });
    connect(SessionManager::instance(), &SessionManager::aboutToSaveSession, this, [] {
        d->saveSession();
    });
}

ProjectManager::~ProjectManager()
{
    EditorManager::setWindowTitleAdditionHandler({});
    EditorManager::setSessionTitleHandler({});
    delete d;
    d = nullptr;
}

ProjectManager *ProjectManager::instance()
{
   return m_instance;
}

bool ProjectManagerPrivate::recursiveDependencyCheck(const FilePath &newDep,
                                                     const FilePath &checkDep) const
{
    if (newDep == checkDep)
        return false;

    const FilePaths depList = m_depMap.value(checkDep);
    for (const FilePath &dependency : depList) {
        if (!recursiveDependencyCheck(newDep, dependency))
            return false;
    }

    return true;
}

/*
 * The dependency management exposes an interface based on projects, but
 * is internally purely string based. This is suboptimal. Probably it would be
 * nicer to map the filenames to projects on load and only map it back to
 * filenames when saving.
 */

QList<Project *> ProjectManager::dependencies(const Project *project)
{
    const FilePath proName = project->projectFilePath();
    const FilePaths proDeps = d->m_depMap.value(proName);

    QList<Project *> projects;
    for (const FilePath &dep : proDeps) {
        Project *pro = Utils::findOrDefault(d->m_projects, [&dep](Project *p) {
            return p->projectFilePath() == dep;
        });
        if (pro)
            projects += pro;
    }

    return projects;
}

bool ProjectManager::hasDependency(const Project *project, const Project *depProject)
{
    const FilePath proName = project->projectFilePath();
    const FilePath depName = depProject->projectFilePath();

    const FilePaths proDeps = d->m_depMap.value(proName);
    return proDeps.contains(depName);
}

bool ProjectManager::canAddDependency(const Project *project, const Project *depProject)
{
    const FilePath newDep = project->projectFilePath();
    const FilePath checkDep = depProject->projectFilePath();

    return d->recursiveDependencyCheck(newDep, checkDep);
}

bool ProjectManager::addDependency(Project *project, Project *depProject)
{
    const FilePath proName = project->projectFilePath();
    const FilePath depName = depProject->projectFilePath();

    // check if this dependency is valid
    if (!d->recursiveDependencyCheck(proName, depName))
        return false;

    FilePaths proDeps = d->m_depMap.value(proName);
    if (!proDeps.contains(depName)) {
        proDeps.append(depName);
        d->m_depMap[proName] = proDeps;
    }
    emit m_instance->dependencyChanged(project, depProject);

    return true;
}

void ProjectManager::removeDependency(Project *project, Project *depProject)
{
    const FilePath proName = project->projectFilePath();
    const FilePath depName = depProject->projectFilePath();

    FilePaths proDeps = d->m_depMap.value(proName);
    proDeps.removeAll(depName);
    if (proDeps.isEmpty())
        d->m_depMap.remove(proName);
    else
        d->m_depMap[proName] = proDeps;
    emit m_instance->dependencyChanged(project, depProject);
}

bool ProjectManager::isProjectConfigurationCascading()
{
    return d->m_casadeSetActive;
}

void ProjectManager::setProjectConfigurationCascading(bool b)
{
    d->m_casadeSetActive = b;
    SessionManager::markSessionFileDirty();
}

bool ProjectManager::deployProjectDependencies()
{
    return d->m_deployProjectDependencies;
}

void ProjectManager::setDeployProjectDependencies(bool deploy)
{
    d->m_deployProjectDependencies = deploy;
    SessionManager::markSessionFileDirty();
}

void ProjectManager::setStartupProject(Project *startupProject)
{
    QTC_ASSERT((!startupProject && d->m_projects.isEmpty())
               || (startupProject && d->m_projects.contains(startupProject)), return);

    if (d->m_startupProject == startupProject)
        return;

    d->m_startupProject = startupProject;
    if (d->m_startupProject && d->m_startupProject->needsConfiguration()) {
        ModeManager::activateMode(Constants::MODE_SESSION);
        ModeManager::setFocusToCurrentMode();
    }
    FolderNavigationWidgetFactory::setFallbackSyncFilePath(
        startupProject ? startupProject->projectFilePath().parentDir() : FilePath());
    emit m_instance->startupProjectChanged(startupProject);
}

Project *ProjectManager::startupProject()
{
    return d->m_startupProject;
}

Target *ProjectManager::startupTarget()
{
    return d->m_startupProject ? d->m_startupProject->activeTarget() : nullptr;
}

void ProjectManager::addProject(Project *pro)
{
    QTC_ASSERT(pro, return);
    QTC_CHECK(!pro->displayName().isEmpty());
    QTC_CHECK(pro->id().isValid());

    SessionManager::markSessionFileDirty();
    QTC_ASSERT(!d->m_projects.contains(pro), return);

    d->m_projects.append(pro);

    connect(pro, &Project::displayNameChanged,
            m_instance, [pro]() { emit m_instance->projectDisplayNameChanged(pro); });

    emit m_instance->projectAdded(pro);
    const auto updateFolderNavigation = [pro] {
        // destructing projects might trigger changes, so check if the project is actually there
        if (QTC_GUARD(d->m_projects.contains(pro))) {
            const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon();
            FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro),
                                                                PROJECT_SORT_VALUE,
                                                                pro->displayName(),
                                                                pro->projectFilePath().parentDir(),
                                                                icon});
        }
    };
    updateFolderNavigation();
    configureEditors(pro);
    connect(pro, &Project::fileListChanged, m_instance, [pro, updateFolderNavigation]() {
        configureEditors(pro);
        updateFolderNavigation(); // update icon
    });
    connect(pro, &Project::displayNameChanged, m_instance, updateFolderNavigation);

    if (!startupProject())
        setStartupProject(pro);
}

void ProjectManager::removeProject(Project *project)
{
    SessionManager::markSessionFileDirty();
    QTC_ASSERT(project, return);
    removeProjects({project});
}

void ProjectManagerPrivate::saveSession()
{
    // save the startup project
    if (d->m_startupProject)
        SessionManager::setSessionValue("StartupProject",
                                        m_startupProject->projectFilePath().toSettings());

    FilePaths projectFiles = Utils::transform(m_projects, &Project::projectFilePath);
    // Restore information on projects that failed to load:
    // don't read projects to the list, which the user loaded
    for (const FilePath &failed : std::as_const(m_failedProjects)) {
        if (!projectFiles.contains(failed))
            projectFiles << failed;
    }

    SessionManager::setSessionValue("ProjectList",
                                    Utils::transform<QStringList>(projectFiles,
                                                                  &FilePath::toUrlishString));
    SessionManager::setSessionValue("CascadeSetActive", m_casadeSetActive);
    SessionManager::setSessionValue("DeployProjectDependencies", m_deployProjectDependencies);

    QVariantMap depMap;
    auto i = m_depMap.constBegin();
    while (i != m_depMap.constEnd()) {
        QString key = i.key().toUrlishString();
        QStringList values;
        const FilePaths valueList = i.value();
        for (const FilePath &value : valueList)
            values << value.toUrlishString();
        depMap.insert(key, values);
        ++i;
    }
    SessionManager::setSessionValue("ProjectDependencies", QVariant(depMap));
}

/*!
  Closes all projects
  */
void ProjectManager::closeAllProjects()
{
    removeProjects(projects());
}

const QList<Project *> ProjectManager::projects()
{
    return d->m_projects;
}

bool ProjectManager::hasProjects()
{
    return d->hasProjects();
}

bool ProjectManager::hasProject(Project *p)
{
    return d->m_projects.contains(p);
}

FilePaths ProjectManagerPrivate::dependencies(const FilePath &proName) const
{
    FilePaths result;
    dependencies(proName, result);
    return result;
}

void ProjectManagerPrivate::dependencies(const FilePath &proName, FilePaths &result) const
{
    const FilePaths depends = m_depMap.value(proName);

    for (const FilePath &dep : depends)
        dependencies(dep, result);

    if (!result.contains(proName))
        result.append(proName);
}

QString ProjectManagerPrivate::sessionTitle(const FilePath &filePath)
{
    const QString sessionName = SessionManager::activeSession();
    if (SessionManager::isDefaultSession(sessionName)) {
        if (filePath.isEmpty()) {
            // use single project's name if there is only one loaded.
            const QList<Project *> projects = ProjectManager::projects();
            if (projects.size() == 1)
                return projects.first()->displayName();
        }
    } else {
        return sessionName.isEmpty() ? Tr::tr("Untitled") : sessionName;
    }
    return {};
}

QString ProjectManagerPrivate::locationInProject(const FilePath &filePath)
{
    const Project *project = ProjectManager::projectForFile(filePath);
    if (!project)
        return {};

    const FilePath parentDir = filePath.parentDir();
    if (parentDir == project->projectDirectory())
        return "@ " + project->displayName();

    if (filePath.isChildOf(project->projectDirectory())) {
        const FilePath dirInProject = parentDir.relativeChildPath(project->projectDirectory());
        return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")";
    }

    // For a file that is "outside" the project it belongs to, we display its
    // dir's full path because it is easier to read than a series of  "../../.".
    // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp
   return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")";
}

QString ProjectManagerPrivate::windowTitleAddition(const FilePath &filePath)
{
    return filePath.isEmpty() ? QString() : locationInProject(filePath);
}

FilePaths ProjectManagerPrivate::dependenciesOrder() const
{
    QList<QPair<FilePath, FilePaths>> unordered;
    FilePaths ordered;

    // copy the map to a temporary list
    for (const Project *pro : m_projects) {
        const FilePath proName = pro->projectFilePath();
        const FilePaths depList = filtered(m_depMap.value(proName),
                                             [this](const FilePath &proPath) {
            return contains(m_projects, [proPath](const Project *p) {
                return p->projectFilePath() == proPath;
            });
        });
        unordered.push_back({proName, depList});
    }

    while (!unordered.isEmpty()) {
        for (int i = (unordered.count() - 1); i >= 0; --i) {
            if (unordered.at(i).second.isEmpty()) {
                ordered << unordered.at(i).first;
                unordered.removeAt(i);
            }
        }

        // remove the handled projects from the dependency lists
        // of the remaining unordered projects
        for (int i = 0; i < unordered.count(); ++i) {
            for (const FilePath &pro : std::as_const(ordered)) {
                FilePaths depList = unordered.at(i).second;
                depList.removeAll(pro);
                unordered[i].second = depList;
            }
        }
    }

    return ordered;
}

QList<Project *> ProjectManager::projectOrder(const Project *project)
{
    QList<Project *> result;

    FilePaths pros;
    if (project)
        pros = d->dependencies(project->projectFilePath());
    else
        pros = d->dependenciesOrder();

    for (const FilePath &proFile : std::as_const(pros)) {
        for (Project *pro : projects()) {
            if (pro->projectFilePath() == proFile) {
                result << pro;
                break;
            }
        }
    }

    return result;
}

Project *ProjectManager::projectForFile(const FilePath &fileName)
{
    if (Project * const project = Utils::findOrDefault(ProjectManager::projects(),
            [&fileName](const Project *p) { return p->isKnownFile(fileName); })) {
        return project;
    }
    return Utils::findOrDefault(ProjectManager::projects(), [&fileName](const Project *p) {
        return isInProjectSourceDir(fileName, *p);
    });
}

QList<Project *> ProjectManager::projectsForFile(const Utils::FilePath &fileName)
{
    return Utils::filtered(ProjectManager::projects(), [&fileName](Project *p) {
        return p->isKnownFile(fileName) || isInProjectSourceDir(fileName, *p);
    });
}

bool ProjectManager::isInProjectBuildDir(const Utils::FilePath &filePath, const Project &project)
{
    for (const Target * const target : project.targets()) {
        for (const BuildConfiguration * const bc : target->buildConfigurations()) {
            if (bc->buildDirectory() == project.projectDirectory())
                continue;
            if (filePath.isChildOf(bc->buildDirectory()))
                return true;
            if (const FilePath canonicalBuildDir = bc->buildDirectory().canonicalPath();
                canonicalBuildDir != bc->buildDirectory() && filePath.isChildOf(canonicalBuildDir)) {
                return true;
            }
        }
    }
    return false;
}

bool ProjectManager::isInProjectSourceDir(const Utils::FilePath &filePath, const Project &project)
{
    if (isInProjectBuildDir(filePath, project))
        return false;
    if (filePath.isChildOf(project.projectDirectory()))
        return true;
    if (const FilePath canonicalRoot = project.projectDirectory().canonicalPath();
        canonicalRoot != project.projectDirectory()) {
        return filePath.isChildOf(canonicalRoot);
    }
    return false;
}

Project *ProjectManager::projectWithProjectFilePath(const FilePath &filePath)
{
    return Utils::findOrDefault(ProjectManager::projects(),
            [&filePath](const Project *p) { return p->projectFilePath() == filePath; });
}

void ProjectManager::configureEditor(IEditor *editor, const FilePath &filePath)
{
    // Global settings are the default.
    if (Project *project = projectForFile(filePath))
        project->editorConfiguration()->configureEditor(editor);
}

void ProjectManager::configureEditors(Project *project)
{
    const QList<IDocument *> documents = DocumentModel::openedDocuments();
    for (IDocument *document : documents) {
        if (project->isKnownFile(document->filePath())) {
            const QList<IEditor *> editors = DocumentModel::editorsForDocument(document);
            for (IEditor *editor : editors)
                project->editorConfiguration()->configureEditor(editor);
        }
    }
}

void ProjectManager::removeProjects(const QList<Project *> &remove)
{
    for (Project *pro : remove) {
        for (const Target * const t : pro->targets()) {
            for (BuildConfiguration * const bc : t->buildConfigurations())
                emit m_instance->aboutToRemoveBuildConfiguration(bc);
        }
        emit m_instance->aboutToRemoveProject(pro);
    }

    bool changeStartupProject = false;

    // Delete projects
    for (Project *pro : remove) {
        pro->saveSettings();
        pro->markAsShuttingDown();

        // Remove the project node:
        d->m_projects.removeOne(pro);

        if (pro == d->m_startupProject)
            changeStartupProject = true;

        FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro));
        disconnect(pro, nullptr, m_instance, nullptr);
        emit m_instance->projectRemoved(pro);
    }

    if (changeStartupProject)
        setStartupProject(hasProjects() ? projects().first() : nullptr);

     qDeleteAll(remove);
}

void ProjectManagerPrivate::restoreDependencies()
{
     QMap<QString, QVariant> depMap = mapEntryFromStoreEntry(SessionManager::sessionValue(
                                                                 "ProjectDependencies")).toMap();
     auto i = depMap.constBegin();
     while (i != depMap.constEnd()) {
        const QString &key = i.key();
        FilePaths values;
        const QStringList valueList = i.value().toStringList();
        for (const QString &value : valueList)
            values << FilePath::fromString(value);
        m_depMap.insert(FilePath::fromString(key), values);
        ++i;
     }
}

void ProjectManagerPrivate::askUserAboutFailedProjects()
{
    FilePaths failedProjects = m_failedProjects;
    if (!failedProjects.isEmpty()) {
        QString fileList = FilePath::formatFilePaths(failedProjects, "<br>");
        QMessageBox box(QMessageBox::Warning,
                                   Tr::tr("Failed to restore project files"),
                                   Tr::tr("Could not restore the following project files:<br><b>%1</b>").
                                   arg(fileList));
        auto keepButton = new QPushButton(Tr::tr("Keep projects in Session"), &box);
        auto removeButton = new QPushButton(Tr::tr("Remove projects from Session"), &box);
        box.addButton(keepButton, QMessageBox::AcceptRole);
        box.addButton(removeButton, QMessageBox::DestructiveRole);

        box.exec();

        if (box.clickedButton() == removeButton)
            m_failedProjects.clear();
    }
}

void ProjectManagerPrivate::restoreStartupProject()
{
    const FilePath startupProject = FilePath::fromSettings(
        SessionManager::sessionValue("StartupProject"));
    if (!startupProject.isEmpty()) {
        for (Project *pro : std::as_const(m_projects)) {
            if (pro->projectFilePath() == startupProject) {
                m_instance->setStartupProject(pro);
                break;
            }
        }
    }
    if (!m_startupProject) {
        if (!startupProject.isEmpty())
            qWarning() << "Could not find startup project" << startupProject;
        if (hasProjects())
            m_instance->setStartupProject(m_projects.first());
    }
}

/*!
     Loads a session, takes a session name (not filename).
*/
void ProjectManagerPrivate::restoreProjects(const FilePaths &fileList)
{
    // indirectly adds projects to session
    // Keep projects that failed to load in the session!
    m_failedProjects = fileList;
    if (!fileList.isEmpty()) {
        OpenProjectResult result = ProjectExplorerPlugin::openProjects(fileList);
        if (!result)
            ProjectExplorerPlugin::showOpenProjectError(result);
        const QList<Project *> projects = result.projects();
        for (const Project *p : projects)
            m_failedProjects.removeAll(p->projectFilePath());
    }
}

void ProjectManagerPrivate::loadSession()
{
    d->m_failedProjects.clear();
    d->m_depMap.clear();
    d->m_casadeSetActive = false;
    d->m_deployProjectDependencies = false;

    // not ideal that this is in ProjectManager
    Id modeId = Id::fromSetting(SessionManager::value("ActiveMode"));
    if (!modeId.isValid())
        modeId = Id(Core::Constants::MODE_EDIT);

    // find a list of projects to close later
    const FilePaths fileList = FileUtils::toFilePathList(
        SessionManager::sessionValue("ProjectList").toStringList());
    const QList<Project *> projectsToRemove
        = Utils::filtered(ProjectManager::projects(), [&fileList](Project *p) {
              return !fileList.contains(p->projectFilePath());
          });
    const QList<Project *> openProjects = ProjectManager::projects();
    const FilePaths projectPathsToLoad
        = Utils::filtered(fileList, [&openProjects](const FilePath &path) {
              return !Utils::contains(openProjects,
                                      [&path](Project *p) { return p->projectFilePath() == path; });
          });

    SessionManager::addSessionLoadingSteps(projectPathsToLoad.count());

    d->restoreProjects(projectPathsToLoad);
    d->restoreDependencies();
    d->restoreStartupProject();

    // only remove old projects now that the startup project is set!
    ProjectManager::removeProjects(projectsToRemove);

    // Fall back to Project mode if the startup project is unconfigured and
    // use the mode saved in the session otherwise
    if (d->m_startupProject && d->m_startupProject->needsConfiguration())
        modeId = Id(Constants::MODE_SESSION);

    ModeManager::activateMode(modeId);
    ModeManager::setFocusToCurrentMode();

    d->m_casadeSetActive = SessionManager::sessionValue("CascadeSetActive", false).toBool();
    d->m_deployProjectDependencies
        = SessionManager::sessionValue("DeployProjectDependencies", false).toBool();

    // Starts a event loop, better do that at the very end
    QMetaObject::invokeMethod(m_instance, [this] { askUserAboutFailedProjects(); });
}

FilePaths ProjectManager::projectsForSessionName(const QString &session)
{
    const FilePath fileName = SessionManager::sessionNameToFileName(session);
    PersistentSettingsReader reader;
    if (fileName.exists()) {
        if (!reader.load(fileName)) {
            qWarning() << "Could not restore session" << fileName.toUserOutput();
            return {};
        }
    }
    return transform(reader.restoreValue("ProjectList").toStringList(), &FilePath::fromUserInput);
}

#ifdef WITH_TESTS

void ProjectExplorerTest::testSessionSwitch()
{
    QVERIFY(SessionManager::createSession("session1"));
    QVERIFY(SessionManager::createSession("session2"));
    QTemporaryFile cppFile("main.cpp");
    QVERIFY(cppFile.open());
    cppFile.close();
    QTemporaryFile projectFile1("XXXXXX.pro");
    QTemporaryFile projectFile2("XXXXXX.pro");
    struct SessionSpec {
        SessionSpec(const QString &n, QTemporaryFile &f) : name(n), projectFile(f) {}
        const QString name;
        QTemporaryFile &projectFile;
    };
    std::vector<SessionSpec> sessionSpecs{SessionSpec("session1", projectFile1),
                SessionSpec("session2", projectFile2)};
    for (const SessionSpec &sessionSpec : sessionSpecs) {
        static const QByteArray proFileContents
                = "TEMPLATE = app\n"
                  "CONFIG -= qt\n"
                  "SOURCES = " + cppFile.fileName().toLocal8Bit();
        QVERIFY(sessionSpec.projectFile.open());
        sessionSpec.projectFile.write(proFileContents);
        sessionSpec.projectFile.close();
        QVERIFY(SessionManager::loadSession(sessionSpec.name));
        const OpenProjectResult openResult
                = ProjectExplorerPlugin::openProject(
                    FilePath::fromString(sessionSpec.projectFile.fileName()));
        if (!ProjectManager::canOpenProjectForMimeType(
                Utils::mimeTypeForFile(sessionSpec.projectFile.fileName()))) {
            QEXPECT_FAIL(
                nullptr,
                "This test requires the presence of QmakeProjectManager to be fully functional. "
                "Hint: run this test with \"-load QmakeProjectManager\" option.",
                Abort);
        }
        QVERIFY2(openResult, qPrintable(openResult.errorMessage()));
        QCOMPARE(openResult.projects().count(), 1);
        QVERIFY(openResult.project());
        QCOMPARE(ProjectManager::projects().count(), 1);
    }
    for (int i = 0; i < 30; ++i) {
        QVERIFY(SessionManager::loadSession("session1"));
        QCOMPARE(SessionManager::activeSession(), "session1");
        QCOMPARE(ProjectManager::projects().count(), 1);
        QVERIFY(SessionManager::loadSession("session2"));
        QCOMPARE(SessionManager::activeSession(), "session2");
        QCOMPARE(ProjectManager::projects().count(), 1);
    }
    QVERIFY(SessionManager::loadSession("session1"));
    ProjectManager::closeAllProjects();
    QVERIFY(SessionManager::loadSession("session2"));
    ProjectManager::closeAllProjects();
    QVERIFY(SessionManager::deleteSession("session1"));
    QVERIFY(SessionManager::deleteSession("session2"));
}

#endif // WITH_TESTS

} // namespace ProjectExplorer
