From d86a05ddc21c8927d3ad93e947df3a7edb28acbe Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 14 Jun 2025 16:10:24 -0500 Subject: [PATCH 1/4] App: Extract BackupPolicy into its own file No code changes except to add AppExport to BackupPolicy --- src/App/BackupPolicy.cpp | 340 +++++++++++++++++++++++++++++++++++++++ src/App/BackupPolicy.h | 68 ++++++++ src/App/CMakeLists.txt | 2 + src/App/Document.cpp | 315 +----------------------------------- 4 files changed, 411 insertions(+), 314 deletions(-) create mode 100644 src/App/BackupPolicy.cpp create mode 100644 src/App/BackupPolicy.h diff --git a/src/App/BackupPolicy.cpp b/src/App/BackupPolicy.cpp new file mode 100644 index 0000000000..ded6ca83f0 --- /dev/null +++ b/src/App/BackupPolicy.cpp @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2020 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "BackupPolicy.h" + +using namespace App; + +void BackupPolicy::setPolicy(const Policy p) +{ + policy = p; +} +void BackupPolicy::setNumberOfFiles(const int count) +{ + numberOfFiles = count; +} +void BackupPolicy::useBackupExtension(const bool on) +{ + useFCBakExtension = on; +} +void BackupPolicy::setDateFormat(const std::string& fmt) +{ + saveBackupDateFormat = fmt; +} +void BackupPolicy::apply(const std::string& sourcename, const std::string& targetname) +{ + switch (policy) { + case Standard: + applyStandard(sourcename, targetname); + break; + case TimeStamp: + applyTimeStamp(sourcename, targetname); + break; + } +} + +void BackupPolicy::applyStandard(const std::string& sourcename, const std::string& targetname) const +{ + // if saving the project data succeeded rename to the actual file name + if (Base::FileInfo fi(targetname); fi.exists()) { + if (numberOfFiles > 0) { + int nSuff = 0; + std::string fn = fi.fileName(); + Base::FileInfo di(fi.dirPath()); + std::vector backup; + std::vector files = di.getDirectoryContent(); + for (const Base::FileInfo& it : files) { + if (std::string file = it.fileName(); file.substr(0, fn.length()) == fn) { + // starts with the same file name + std::string suf(file.substr(fn.length())); + if (!suf.empty()) { + std::string::size_type nPos = suf.find_first_not_of("0123456789"); + if (nPos == std::string::npos) { + // store all backup files + backup.push_back(it); + nSuff = + std::max(nSuff, static_cast(std::atol(suf.c_str()))); + } + } + } + } + + if (!backup.empty() && static_cast(backup.size()) >= numberOfFiles) { + // delete the oldest backup file we found + Base::FileInfo del = backup.front(); + for (const Base::FileInfo& it : backup) { + if (it.lastModified() < del.lastModified()) { + del = it; + } + } + + del.deleteFile(); + fn = del.filePath(); + } + else { + // create a new backup file + std::stringstream str; + str << fi.filePath() << (nSuff + 1); + fn = str.str(); + } + + if (!fi.renameFile(fn.c_str())) { + Base::Console().warning("Cannot rename project file to backup file\n"); + } + } + else { + fi.deleteFile(); + } + } + + if (Base::FileInfo tmp(sourcename); !tmp.renameFile(targetname.c_str())) { + throw Base::FileException("Cannot rename tmp save file to project file", + Base::FileInfo(targetname)); + } +} + +void BackupPolicy::applyTimeStamp(const std::string& sourcename, const std::string& targetname) +{ + Base::FileInfo fi(targetname); + + std::string fn = sourcename; + std::string ext = fi.extension(); + std::string bn; // full path with no extension but with "." + std::string pbn; // base name of the project + "." + if (!ext.empty()) { + bn = fi.filePath().substr(0, fi.filePath().length() - ext.length()); + pbn = fi.fileName().substr(0, fi.fileName().length() - ext.length()); + } + else { + bn = fi.filePath() + "."; + pbn = fi.fileName() + "."; + } + + bool backupManagementError = false; // Note error and report at the end + if (fi.exists()) { + if (numberOfFiles > 0) { + // replace . by - in format to avoid . between base name and extension + boost::replace_all(saveBackupDateFormat, ".", "-"); + { + // Remove all extra backups + std::string filename = fi.fileName(); + Base::FileInfo di(fi.dirPath()); + std::vector backup; + std::vector files = di.getDirectoryContent(); + for (const Base::FileInfo& it : files) { + if (it.isFile()) { + std::string file = it.fileName(); + std::string fext = it.extension(); + std::string fextUp = fext; + std::transform(fextUp.begin(), + fextUp.end(), + fextUp.begin(), + static_cast(toupper)); + // re-enforcing identification of the backup file + + + // old case : the name starts with the full name of the project and + // follows with numbers + if ((startsWith(file, filename) && (file.length() > filename.length()) + && checkDigits(file.substr(filename.length()))) + || + // .FCBak case : The bame starts with the base name of the project + + // "." + // + complement with no "." + ".FCBak" + ((fextUp == "FCBAK") && startsWith(file, pbn) + && (checkValidComplement(file, pbn, fext)))) { + backup.push_back(it); + } + } + } + + if (!backup.empty() && static_cast(backup.size()) >= numberOfFiles) { + std::sort(backup.begin(), backup.end(), fileComparisonByDate); + // delete the oldest backup file we found + // Base::FileInfo del = backup.front(); + int nb = 0; + for (Base::FileInfo& it : backup) { + nb++; + if (nb >= numberOfFiles) { + try { + if (!it.deleteFile()) { + backupManagementError = true; + Base::Console().warning("Cannot remove backup file : %s\n", + it.fileName().c_str()); + } + } + catch (...) { + backupManagementError = true; + Base::Console().warning("Cannot remove backup file : %s\n", + it.fileName().c_str()); + } + } + } + } + } // end remove backup + + // create a new backup file + { + int ext2 = 1; + if (useFCBakExtension) { + std::stringstream str; + Base::TimeInfo ti = fi.lastModified(); + time_t s = ti.getTime_t(); + struct tm* timeinfo = localtime(&s); + char buffer[100]; + + strftime(buffer, sizeof(buffer), saveBackupDateFormat.c_str(), timeinfo); + str << bn << buffer; + + fn = str.str(); + bool done = false; + + if ((fn.empty()) || (fn[fn.length() - 1] == ' ') + || (fn[fn.length() - 1] == '-')) { + if (fn[fn.length() - 1] == ' ') { + fn = fn.substr(0, fn.length() - 1); + } + } + else { + if (!renameFileNoErase(fi, fn + ".FCBak")) { + fn = fn + "-"; + } + else { + done = true; + } + } + + if (!done) { + while (ext2 < numberOfFiles + 10) { + if (renameFileNoErase(fi, fn + std::to_string(ext2) + ".FCBak")) { + break; + } + ext2++; + } + } + } + else { + // changed but simpler and solves also the delay sometimes introduced by + // google drive + while (ext2 < numberOfFiles + 10) { + // linux just replace the file if exists, and then the existence is to + // be tested before rename + if (renameFileNoErase(fi, fi.filePath() + std::to_string(ext2))) { + break; + } + ext2++; + } + } + + if (ext2 >= numberOfFiles + 10) { + Base::Console().error( + "File not saved: Cannot rename project file to backup file\n"); + // throw Base::FileException("File not saved: Cannot rename project file to + // backup file", fi); + } + } + } + else { + try { + fi.deleteFile(); + } + catch (...) { + Base::Console().warning("Cannot remove backup file: %s\n", + fi.fileName().c_str()); + backupManagementError = true; + } + } + } + + Base::FileInfo tmp(sourcename); + if (!tmp.renameFile(targetname.c_str())) { + throw Base::FileException( + "Save interrupted: Cannot rename temporary file to project file", + tmp); + } + + if (backupManagementError) { + throw Base::FileException( + "Warning: Save complete, but error while managing backup history.", + fi); + } +} + +bool BackupPolicy::fileComparisonByDate(const Base::FileInfo& i, const Base::FileInfo& j) +{ + return (i.lastModified() > j.lastModified()); +} + +bool BackupPolicy::startsWith(const std::string& st1, const std::string& st2) const +{ + return st1.substr(0, st2.length()) == st2; +} + +bool BackupPolicy::checkValidString(const std::string& cmpl, const boost::regex& e) const +{ + boost::smatch what; + const bool res = boost::regex_search(cmpl, what, e); + return res; +} + +bool BackupPolicy::checkValidComplement(const std::string& file, + const std::string& pbn, + const std::string& ext) const +{ + const std::string cmpl = + file.substr(pbn.length(), file.length() - pbn.length() - ext.length() - 1); + const boost::regex e(R"(^[^.]*$)"); + return checkValidString(cmpl, e); +} + +bool BackupPolicy::checkDigits(const std::string& cmpl) const +{ + const boost::regex e(R"(^[0-9]*$)"); + return checkValidString(cmpl, e); +} + +bool BackupPolicy::renameFileNoErase(Base::FileInfo fi, const std::string& newName) +{ + // linux just replaces the file if it exists, so the existence is to be tested before rename + const Base::FileInfo nf(newName); + if (!nf.exists()) { + return fi.renameFile(newName.c_str()); + } + return false; +} diff --git a/src/App/BackupPolicy.h b/src/App/BackupPolicy.h new file mode 100644 index 0000000000..d403b88171 --- /dev/null +++ b/src/App/BackupPolicy.h @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2020 Werner Mayer * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include "FCGlobal.h" + +#include +#include +#include +#include + +namespace App +{ +// Helper class to handle different backup policies: originally a private class in Document.cpp, +// and extracted for public access to enable direct testing since the logic involved is quite +// complex. +class AppExport BackupPolicy +{ +public: + enum Policy + { + Standard, + TimeStamp + }; + void setPolicy(const Policy p); + void setNumberOfFiles(const int count); + void useBackupExtension(const bool on); + void setDateFormat(const std::string& fmt); + void apply(const std::string& sourcename, const std::string& targetname); + +private: + void applyStandard(const std::string& sourcename, const std::string& targetname) const; + void applyTimeStamp(const std::string& sourcename, const std::string& targetname); + static bool fileComparisonByDate(const Base::FileInfo& i, const Base::FileInfo& j); + bool startsWith(const std::string& st1, const std::string& st2) const; + bool checkValidString(const std::string& cmpl, const boost::regex& e) const; + bool checkValidComplement(const std::string& file, + const std::string& pbn, + const std::string& ext) const; + bool checkDigits(const std::string& cmpl) const; + bool renameFileNoErase(Base::FileInfo fi, const std::string& newName); + +private: + Policy policy {Standard}; + int numberOfFiles {1}; + bool useFCBakExtension {true}; + std::string saveBackupDateFormat {"%Y%m%d-%H%M%S"}; +}; +} // namespace App \ No newline at end of file diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index 6230c42755..416740dcb3 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -133,6 +133,7 @@ SOURCE_GROUP("Pyi" FILES ${FreeCADApp_Pyi_SRCS}) # The document stuff SET(Document_CPP_SRCS Annotation.cpp + BackupPolicy.cpp Document.cpp DocumentObject.cpp Extension.cpp @@ -187,6 +188,7 @@ SET(Document_CPP_SRCS SET(Document_HPP_SRCS Annotation.h + BackupPolicy.h Document.h DocumentObject.h Extension.h diff --git a/src/App/Document.cpp b/src/App/Document.cpp index 57458dd9fd..d1cf886f92 100644 --- a/src/App/Document.cpp +++ b/src/App/Document.cpp @@ -70,6 +70,7 @@ #include "private/DocumentP.h" #include "Application.h" #include "AutoTransaction.h" +#include "BackupPolicy.h" #include "ExpressionParser.h" #include "GeoFeature.h" #include "License.h" @@ -1724,320 +1725,6 @@ bool Document::save() return false; } -namespace App -{ -// Helper class to handle different backup policies -class BackupPolicy -{ -public: - enum Policy - { - Standard, - TimeStamp - }; - BackupPolicy() - {} - ~BackupPolicy() = default; - void setPolicy(const Policy p) - { - policy = p; - } - void setNumberOfFiles(const int count) - { - numberOfFiles = count; - } - void useBackupExtension(const bool on) - { - useFCBakExtension = on; - } - void setDateFormat(const std::string& fmt) - { - saveBackupDateFormat = fmt; - } - void apply(const std::string& sourcename, const std::string& targetname) - { - switch (policy) { - case Standard: - applyStandard(sourcename, targetname); - break; - case TimeStamp: - applyTimeStamp(sourcename, targetname); - break; - } - } - -private: - void applyStandard(const std::string& sourcename, const std::string& targetname) const - { - // if saving the project data succeeded rename to the actual file name - if (Base::FileInfo fi(targetname); fi.exists()) { - if (numberOfFiles > 0) { - int nSuff = 0; - std::string fn = fi.fileName(); - Base::FileInfo di(fi.dirPath()); - std::vector backup; - std::vector files = di.getDirectoryContent(); - for (const Base::FileInfo& it : files) { - if (std::string file = it.fileName(); file.substr(0, fn.length()) == fn) { - // starts with the same file name - std::string suf(file.substr(fn.length())); - if (!suf.empty()) { - std::string::size_type nPos = suf.find_first_not_of("0123456789"); - if (nPos == std::string::npos) { - // store all backup files - backup.push_back(it); - nSuff = - std::max(nSuff, static_cast(std::atol(suf.c_str()))); - } - } - } - } - - if (!backup.empty() && static_cast(backup.size()) >= numberOfFiles) { - // delete the oldest backup file we found - Base::FileInfo del = backup.front(); - for (const Base::FileInfo& it : backup) { - if (it.lastModified() < del.lastModified()) { - del = it; - } - } - - del.deleteFile(); - fn = del.filePath(); - } - else { - // create a new backup file - std::stringstream str; - str << fi.filePath() << (nSuff + 1); - fn = str.str(); - } - - if (!fi.renameFile(fn.c_str())) { - Base::Console().warning("Cannot rename project file to backup file\n"); - } - } - else { - fi.deleteFile(); - } - } - - if (Base::FileInfo tmp(sourcename); !tmp.renameFile(targetname.c_str())) { - throw Base::FileException("Cannot rename tmp save file to project file", - Base::FileInfo(targetname)); - } - } - void applyTimeStamp(const std::string& sourcename, const std::string& targetname) - { - Base::FileInfo fi(targetname); - - std::string fn = sourcename; - std::string ext = fi.extension(); - std::string bn; // full path with no extension but with "." - std::string pbn; // base name of the project + "." - if (!ext.empty()) { - bn = fi.filePath().substr(0, fi.filePath().length() - ext.length()); - pbn = fi.fileName().substr(0, fi.fileName().length() - ext.length()); - } - else { - bn = fi.filePath() + "."; - pbn = fi.fileName() + "."; - } - - bool backupManagementError = false; // Note error and report at the end - if (fi.exists()) { - if (numberOfFiles > 0) { - // replace . by - in format to avoid . between base name and extension - boost::replace_all(saveBackupDateFormat, ".", "-"); - { - // Remove all extra backups - std::string filename = fi.fileName(); - Base::FileInfo di(fi.dirPath()); - std::vector backup; - std::vector files = di.getDirectoryContent(); - for (const Base::FileInfo& it : files) { - if (it.isFile()) { - std::string file = it.fileName(); - std::string fext = it.extension(); - std::string fextUp = fext; - std::transform(fextUp.begin(), - fextUp.end(), - fextUp.begin(), - static_cast(toupper)); - // re-enforcing identification of the backup file - - - // old case : the name starts with the full name of the project and - // follows with numbers - if ((startsWith(file, filename) && (file.length() > filename.length()) - && checkDigits(file.substr(filename.length()))) - || - // .FCBak case : The bame starts with the base name of the project + - // "." - // + complement with no "." + ".FCBak" - ((fextUp == "FCBAK") && startsWith(file, pbn) - && (checkValidComplement(file, pbn, fext)))) { - backup.push_back(it); - } - } - } - - if (!backup.empty() && static_cast(backup.size()) >= numberOfFiles) { - std::sort(backup.begin(), backup.end(), fileComparisonByDate); - // delete the oldest backup file we found - // Base::FileInfo del = backup.front(); - int nb = 0; - for (Base::FileInfo& it : backup) { - nb++; - if (nb >= numberOfFiles) { - try { - if (!it.deleteFile()) { - backupManagementError = true; - Base::Console().warning("Cannot remove backup file : %s\n", - it.fileName().c_str()); - } - } - catch (...) { - backupManagementError = true; - Base::Console().warning("Cannot remove backup file : %s\n", - it.fileName().c_str()); - } - } - } - } - } // end remove backup - - // create a new backup file - { - int ext2 = 1; - if (useFCBakExtension) { - std::stringstream str; - Base::TimeInfo ti = fi.lastModified(); - time_t s = ti.getTime_t(); - struct tm* timeinfo = localtime(&s); - char buffer[100]; - - strftime(buffer, sizeof(buffer), saveBackupDateFormat.c_str(), timeinfo); - str << bn << buffer; - - fn = str.str(); - bool done = false; - - if ((fn.empty()) || (fn[fn.length() - 1] == ' ') - || (fn[fn.length() - 1] == '-')) { - if (fn[fn.length() - 1] == ' ') { - fn = fn.substr(0, fn.length() - 1); - } - } - else { - if (!renameFileNoErase(fi, fn + ".FCBak")) { - fn = fn + "-"; - } - else { - done = true; - } - } - - if (!done) { - while (ext2 < numberOfFiles + 10) { - if (renameFileNoErase(fi, fn + std::to_string(ext2) + ".FCBak")) { - break; - } - ext2++; - } - } - } - else { - // changed but simpler and solves also the delay sometimes introduced by - // google drive - while (ext2 < numberOfFiles + 10) { - // linux just replace the file if exists, and then the existence is to - // be tested before rename - if (renameFileNoErase(fi, fi.filePath() + std::to_string(ext2))) { - break; - } - ext2++; - } - } - - if (ext2 >= numberOfFiles + 10) { - Base::Console().error( - "File not saved: Cannot rename project file to backup file\n"); - // throw Base::FileException("File not saved: Cannot rename project file to - // backup file", fi); - } - } - } - else { - try { - fi.deleteFile(); - } - catch (...) { - Base::Console().warning("Cannot remove backup file: %s\n", - fi.fileName().c_str()); - backupManagementError = true; - } - } - } - - Base::FileInfo tmp(sourcename); - if (!tmp.renameFile(targetname.c_str())) { - throw Base::FileException( - "Save interrupted: Cannot rename temporary file to project file", - tmp); - } - - if (backupManagementError) { - throw Base::FileException( - "Warning: Save complete, but error while managing backup history.", - fi); - } - } - static bool fileComparisonByDate(const Base::FileInfo& i, const Base::FileInfo& j) - { - return (i.lastModified() > j.lastModified()); - } - bool startsWith(const std::string& st1, const std::string& st2) const - { - return st1.substr(0, st2.length()) == st2; - } - bool checkValidString(const std::string& cmpl, const boost::regex& e) const - { - boost::smatch what; - const bool res = boost::regex_search(cmpl, what, e); - return res; - } - bool checkValidComplement(const std::string& file, - const std::string& pbn, - const std::string& ext) const - { - const std::string cmpl = - file.substr(pbn.length(), file.length() - pbn.length() - ext.length() - 1); - const boost::regex e(R"(^[^.]*$)"); - return checkValidString(cmpl, e); - } - bool checkDigits(const std::string& cmpl) const - { - const boost::regex e(R"(^[0-9]*$)"); - return checkValidString(cmpl, e); - } - bool renameFileNoErase(Base::FileInfo fi, const std::string& newName) - { - // linux just replaces the file if it exists, so the existence is to be tested before rename - const Base::FileInfo nf(newName); - if (!nf.exists()) { - return fi.renameFile(newName.c_str()); - } - return false; - } - -private: - Policy policy {Standard}; - int numberOfFiles {1}; - bool useFCBakExtension {true}; - std::string saveBackupDateFormat {"%Y%m%d-%H%M%S"}; -}; -} // namespace App - bool Document::saveToFile(const char* filename) const { signalStartSave(*this, filename); From 376b162239478f8b8cd8475d2211736d3276e5a4 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sat, 14 Jun 2025 22:26:27 -0500 Subject: [PATCH 2/4] Tests: Add test framework for BackupPolicy --- tests/src/App/BackupPolicy.cpp | 116 +++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 tests/src/App/BackupPolicy.cpp diff --git a/tests/src/App/BackupPolicy.cpp b/tests/src/App/BackupPolicy.cpp new file mode 100644 index 0000000000..94c85e8ec2 --- /dev/null +++ b/tests/src/App/BackupPolicy.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +/**************************************************************************** + * Copyright (c) 2025 The FreeCAD project association AISBL * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#include +#include + +#include "InitApplication.h" + +#include + +#include +#include +#include +#include + + +class BackupPolicyTest: public ::testing::Test +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + void SetUp() override + { + _tempDir = std::filesystem::temp_directory_path() / ("fc_backup_policy-" + randomString(16)); + std::filesystem::create_directory(_tempDir); + } + + void TearDown() override + { + std::filesystem::remove_all(_tempDir); + } + + void apply(const std::string& sourcename, const std::string& targetname) + { + _policy.apply(sourcename, targetname); + } + + void setPolicyTerms(App::BackupPolicy::Policy p, int count, bool useExt, const std::string& fmt) + { + _policy.setPolicy(p); + _policy.setNumberOfFiles(count); + _policy.useBackupExtension(useExt); + _policy.setDateFormat(fmt); + } + + // Create a named temporary file: returns the full path to the new file. Deleted by the TearDown + // method at the end of the test. + std::filesystem::path createTempFile(const std::string& filename) + { + std::filesystem::path p = _tempDir / filename; + std::ofstream fileStream(p.string()); + fileStream << "Test data"; + fileStream.close(); + return p; + } + + +private: + + std::string randomString(size_t length) + { + static constexpr std::string_view chars = + "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, static_cast(chars.size()) - 1); + + std::string result; + result.reserve(length); + + std::ranges::generate_n(std::back_inserter(result), length, [&]() { + return chars[dis(gen)]; + }); + + return result; + } + + App::BackupPolicy _policy; + std::filesystem::path _tempDir; + +}; + +TEST_F(BackupPolicyTest, StandardSourceDoesNotExist) +{ + // Arrange + setPolicyTerms(App::BackupPolicy::Policy::Standard, 1, true, "%Y-%m-%d_%H-%M-%S"); + + // Act & Assert + EXPECT_THROW(apply("nonexistent.fcstd", "backup.fcstd"), Base::FileException); +} \ No newline at end of file From 98eee8a56363a048cb4ff5d7c3fb0bf9504a9a6a Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 15 Jun 2025 10:29:41 -0500 Subject: [PATCH 3/4] Tests: Refactor tests with better names, etc. The basic tests now run on Windows correctly without needing to manually copy any files -- the executables are output in the same location as the necessary DLLs. However, tests that require *.pyd files (all code in the Mod subdirectory, basically) still do not work on Windows as the test executables cannot find those files. This is a work in progress. --- tests/CMakeLists.txt | 68 ++++++++++++-------- tests/src/App/BackupPolicy.cpp | 14 ++-- tests/src/App/CMakeLists.txt | 14 +++- tests/src/Base/CMakeLists.txt | 9 ++- tests/src/Gui/CMakeLists.txt | 11 +++- tests/src/Misc/CMakeLists.txt | 9 ++- tests/src/Misc/fmt.cpp | 4 +- tests/src/Mod/Assembly/App/CMakeLists.txt | 2 +- tests/src/Mod/Assembly/CMakeLists.txt | 3 +- tests/src/Mod/Material/App/CMakeLists.txt | 2 +- tests/src/Mod/Material/CMakeLists.txt | 4 +- tests/src/Mod/Measure/App/CMakeLists.txt | 2 +- tests/src/Mod/Measure/CMakeLists.txt | 4 +- tests/src/Mod/Mesh/App/CMakeLists.txt | 6 +- tests/src/Mod/Mesh/CMakeLists.txt | 4 +- tests/src/Mod/MeshPart/App/CMakeLists.txt | 2 +- tests/src/Mod/MeshPart/CMakeLists.txt | 4 +- tests/src/Mod/Part/App/CMakeLists.txt | 2 +- tests/src/Mod/Part/CMakeLists.txt | 4 +- tests/src/Mod/PartDesign/App/CMakeLists.txt | 2 +- tests/src/Mod/PartDesign/CMakeLists.txt | 4 +- tests/src/Mod/Points/App/CMakeLists.txt | 2 +- tests/src/Mod/Points/CMakeLists.txt | 4 +- tests/src/Mod/Sketcher/App/CMakeLists.txt | 2 +- tests/src/Mod/Sketcher/CMakeLists.txt | 4 +- tests/src/Mod/Spreadsheet/App/CMakeLists.txt | 2 +- tests/src/Mod/Spreadsheet/CMakeLists.txt | 4 +- tests/src/Mod/Start/App/CMakeLists.txt | 8 +-- tests/src/Mod/Start/CMakeLists.txt | 3 +- tests/src/zipios++/CMakeLists.txt | 22 +++++-- 30 files changed, 138 insertions(+), 87 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 069498dfa8..8a83ceeaf2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,7 +41,7 @@ if(MSVC) endif() if(WIN32) - add_definitions(-DCOIN_DLL -D_USE_MATH_DEFINES) + add_definitions(-DCOIN_DLL) endif(WIN32) if(NOT BUILD_DYNAMIC_LINK_PYTHON) @@ -78,69 +78,83 @@ function(setup_qt_test) endforeach() endfunction() -# Add test executables here - set(TestExecutables - Tests_run + App_tests_run + Base_tests_run + Misc_tests_run + Zipios_tests_run ) +# NOTE: The following tests don't yet run on Windows because they can't find the *.pyd files needed by each FreeCAD +# module. + +if(BUILD_GUI) + list (APPEND TestExecutables Gui_tests_run) +endif() if(BUILD_ASSEMBLY) - list (APPEND TestExecutables Assembly_tests_run) + list (APPEND TestExecutables Assembly_tests_run) endif(BUILD_ASSEMBLY) if(BUILD_MATERIAL) - list (APPEND TestExecutables Material_tests_run) + list (APPEND TestExecutables Material_tests_run) endif(BUILD_MATERIAL) if(BUILD_MEASURE) - list (APPEND TestExecutables Measure_tests_run) + list (APPEND TestExecutables Measure_tests_run) endif(BUILD_MEASURE) if(BUILD_MESH) - list (APPEND TestExecutables Mesh_tests_run) + list (APPEND TestExecutables Mesh_tests_run) endif(BUILD_MESH) if(BUILD_MESH_PART) - list (APPEND TestExecutables MeshPart_tests_run) + list (APPEND TestExecutables MeshPart_tests_run) endif(BUILD_MESH_PART) if(BUILD_PART) - list (APPEND TestExecutables Part_tests_run) + list (APPEND TestExecutables Part_tests_run) endif(BUILD_PART) if(BUILD_PART_DESIGN) list (APPEND TestExecutables PartDesign_tests_run) endif(BUILD_PART_DESIGN) if(BUILD_POINTS) - list (APPEND TestExecutables Points_tests_run) + list (APPEND TestExecutables Points_tests_run) endif(BUILD_POINTS) if(BUILD_SKETCHER) - list (APPEND TestExecutables Sketcher_tests_run) + list (APPEND TestExecutables Sketcher_tests_run) endif(BUILD_SKETCHER) if(BUILD_SPREADSHEET) - list (APPEND TestExecutables Spreadsheet_tests_run) + list (APPEND TestExecutables Spreadsheet_tests_run) endif() if(BUILD_START) - list (APPEND TestExecutables Start_tests_run) + list (APPEND TestExecutables Start_tests_run) endif() # ------------------------- -foreach (exe ${TestExecutables}) - add_executable(${exe}) -endforeach() - if ( NOT FREECAD_USE_EXTERNAL_GTEST ) + if(WIN32) + set(BUILD_SHARED_LIBS OFF) + endif() add_subdirectory(lib) endif() add_subdirectory(src) -target_link_libraries(Tests_run - gtest_main - gmock_main - ${Google_Tests_LIBS} - FreeCADApp - FreeCADGui -) - include(GoogleTest) -# discovers tests by asking the compiled test executable to enumerate its tests set(CMAKE_GTEST_DISCOVER_TESTS_DISCOVERY_MODE PRE_TEST) foreach (exe ${TestExecutables}) + if(WIN32) + # On Windows the test executables need to be in the same place as all the other DLLs that are getting built + if(CMAKE_CONFIGURATION_TYPES) + # Visual Studio solution file, supports switching configs on the fly in the IDE + set(OUTPUT_DIR ${CMAKE_BINARY_DIR}/bin) + foreach(OUTPUT_CONFIG Debug Release RelWithDebInfo MinSizeRel) + string(TOUPPER "${OUTPUT_CONFIG}" UPPER_CONFIG) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${UPPER_CONFIG} ${OUTPUT_DIR}/${OUTPUT_CONFIG}) + endforeach() + else() + # Ninja (usually), e.g. when using CLion with MSVC toolchain, etc. is actually single-config + set_target_properties(${exe} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + set_target_properties(${exe} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + endif() + else() + set_target_properties(${exe} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests) + endif() gtest_discover_tests(${exe}) endforeach() diff --git a/tests/src/App/BackupPolicy.cpp b/tests/src/App/BackupPolicy.cpp index 94c85e8ec2..10679df6a7 100644 --- a/tests/src/App/BackupPolicy.cpp +++ b/tests/src/App/BackupPolicy.cpp @@ -44,7 +44,8 @@ protected: void SetUp() override { - _tempDir = std::filesystem::temp_directory_path() / ("fc_backup_policy-" + randomString(16)); + _tempDir = + std::filesystem::temp_directory_path() / ("fc_backup_policy-" + randomString(16)); std::filesystem::create_directory(_tempDir); } @@ -79,13 +80,11 @@ protected: private: - std::string randomString(size_t length) { - static constexpr std::string_view chars = - "0123456789" - "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static constexpr std::string_view chars = "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::random_device rd; std::mt19937 gen(rd()); @@ -103,7 +102,6 @@ private: App::BackupPolicy _policy; std::filesystem::path _tempDir; - }; TEST_F(BackupPolicyTest, StandardSourceDoesNotExist) @@ -113,4 +111,4 @@ TEST_F(BackupPolicyTest, StandardSourceDoesNotExist) // Act & Assert EXPECT_THROW(apply("nonexistent.fcstd", "backup.fcstd"), Base::FileException); -} \ No newline at end of file +} diff --git a/tests/src/App/CMakeLists.txt b/tests/src/App/CMakeLists.txt index 6ba90417b2..182c9152ef 100644 --- a/tests/src/App/CMakeLists.txt +++ b/tests/src/App/CMakeLists.txt @@ -1,7 +1,6 @@ -target_compile_definitions(Tests_run PRIVATE DATADIR="${CMAKE_SOURCE_DIR}/data") - -target_sources(Tests_run PRIVATE +add_executable(App_tests_run Application.cpp + BackupPolicy.cpp Branding.cpp ComplexGeoData.cpp Document.cpp @@ -24,3 +23,12 @@ target_sources(Tests_run PRIVATE VarSet.cpp VRMLObject.cpp ) + +target_compile_definitions(App_tests_run PRIVATE DATADIR="${CMAKE_SOURCE_DIR}/data") + +target_link_libraries(App_tests_run PRIVATE + GTest::gtest_main + GTest::gmock_main + ${Google_Tests_LIBS} + FreeCADApp +) diff --git a/tests/src/Base/CMakeLists.txt b/tests/src/Base/CMakeLists.txt index 9bc17b29ec..0bdc34ba5c 100644 --- a/tests/src/Base/CMakeLists.txt +++ b/tests/src/Base/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Tests_run PRIVATE +add_executable(Base_tests_run Axis.cpp Base64.cpp Bitmask.cpp @@ -31,3 +31,10 @@ target_sources(Tests_run PRIVATE ) setup_qt_test(InventorBuilder) + +target_link_libraries(Base_tests_run PRIVATE + GTest::gtest_main + GTest::gmock_main + ${Google_Tests_LIBS} + FreeCADApp +) diff --git a/tests/src/Gui/CMakeLists.txt b/tests/src/Gui/CMakeLists.txt index 774cf35f31..499ee7ba9f 100644 --- a/tests/src/Gui/CMakeLists.txt +++ b/tests/src/Gui/CMakeLists.txt @@ -1,8 +1,17 @@ # Standard C++ GTest tests -target_sources(Tests_run PRIVATE +add_executable(Gui_tests_run Assistant.cpp Camera.cpp ) # Qt tests setup_qt_test(QuantitySpinBox) + + +target_link_libraries(Gui_tests_run PRIVATE + GTest::gtest_main + GTest::gmock_main + ${Google_Tests_LIBS} + FreeCADApp + FreeCADGui +) diff --git a/tests/src/Misc/CMakeLists.txt b/tests/src/Misc/CMakeLists.txt index 6688fc7546..33cf8f1d8d 100644 --- a/tests/src/Misc/CMakeLists.txt +++ b/tests/src/Misc/CMakeLists.txt @@ -1,3 +1,10 @@ -target_sources(Tests_run PRIVATE +add_executable(Misc_tests_run fmt.cpp ) + +target_link_libraries(Misc_tests_run PRIVATE + GTest::gtest_main + GTest::gmock_main + ${Google_Tests_LIBS} + fmt::fmt +) diff --git a/tests/src/Misc/fmt.cpp b/tests/src/Misc/fmt.cpp index 778e03b7ad..8113745a74 100644 --- a/tests/src/Misc/fmt.cpp +++ b/tests/src/Misc/fmt.cpp @@ -1,5 +1,5 @@ -#include "fmt/format.h" -#include "fmt/printf.h" +#include +#include #include #include diff --git a/tests/src/Mod/Assembly/App/CMakeLists.txt b/tests/src/Mod/Assembly/App/CMakeLists.txt index 0d4135aff8..8b9a91cceb 100644 --- a/tests/src/Mod/Assembly/App/CMakeLists.txt +++ b/tests/src/Mod/Assembly/App/CMakeLists.txt @@ -1,3 +1,3 @@ -target_sources(Assembly_tests_run PRIVATE +add_executable(Assembly_tests_run AssemblyObject.cpp ) diff --git a/tests/src/Mod/Assembly/CMakeLists.txt b/tests/src/Mod/Assembly/CMakeLists.txt index ea1936eb89..42fbe630be 100644 --- a/tests/src/Mod/Assembly/CMakeLists.txt +++ b/tests/src/Mod/Assembly/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(App) if (NOT FREECAD_USE_EXTERNAL_ONDSELSOLVER) target_include_directories(Assembly_tests_run PUBLIC @@ -10,5 +11,3 @@ target_link_libraries(Assembly_tests_run ${Google_Tests_LIBS} Assembly ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Material/App/CMakeLists.txt b/tests/src/Mod/Material/App/CMakeLists.txt index b6b4cd949a..8e9b748285 100644 --- a/tests/src/Mod/Material/App/CMakeLists.txt +++ b/tests/src/Mod/Material/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Material_tests_run PRIVATE +add_executable(Material_tests_run TestMaterialCards.cpp TestMaterialFilter.cpp TestMaterialProperties.cpp diff --git a/tests/src/Mod/Material/CMakeLists.txt b/tests/src/Mod/Material/CMakeLists.txt index b592fcd16d..4e309e4f55 100644 --- a/tests/src/Mod/Material/CMakeLists.txt +++ b/tests/src/Mod/Material/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Material_tests_run gtest_main ${Google_Tests_LIBS} Materials ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Measure/App/CMakeLists.txt b/tests/src/Mod/Measure/App/CMakeLists.txt index 72857fce11..523974b8e2 100644 --- a/tests/src/Mod/Measure/App/CMakeLists.txt +++ b/tests/src/Mod/Measure/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Measure_tests_run PRIVATE +add_executable(Measure_tests_run MeasureDistance.cpp ) diff --git a/tests/src/Mod/Measure/CMakeLists.txt b/tests/src/Mod/Measure/CMakeLists.txt index 5c3bffe472..12bce0f024 100644 --- a/tests/src/Mod/Measure/CMakeLists.txt +++ b/tests/src/Mod/Measure/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Measure_tests_run gtest_main ${Google_Tests_LIBS} Measure ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Mesh/App/CMakeLists.txt b/tests/src/Mod/Mesh/App/CMakeLists.txt index d90597b62e..b4f6a04ca6 100644 --- a/tests/src/Mod/Mesh/App/CMakeLists.txt +++ b/tests/src/Mod/Mesh/App/CMakeLists.txt @@ -1,9 +1,9 @@ -target_compile_definitions(Mesh_tests_run PRIVATE DATADIR="${CMAKE_SOURCE_DIR}/data") - -target_sources(Mesh_tests_run PRIVATE +add_executable(Mesh_tests_run Core/KDTree.cpp Exporter.cpp Importer.cpp Mesh.cpp MeshFeature.cpp ) + +target_compile_definitions(Mesh_tests_run PRIVATE DATADIR="${CMAKE_SOURCE_DIR}/data") diff --git a/tests/src/Mod/Mesh/CMakeLists.txt b/tests/src/Mod/Mesh/CMakeLists.txt index 8892bc2af4..a1b44216ee 100644 --- a/tests/src/Mod/Mesh/CMakeLists.txt +++ b/tests/src/Mod/Mesh/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Mesh_tests_run gtest_main ${Google_Tests_LIBS} Mesh ) - -add_subdirectory(App) diff --git a/tests/src/Mod/MeshPart/App/CMakeLists.txt b/tests/src/Mod/MeshPart/App/CMakeLists.txt index f9c003744a..96f4eadc4f 100644 --- a/tests/src/Mod/MeshPart/App/CMakeLists.txt +++ b/tests/src/Mod/MeshPart/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(MeshPart_tests_run PRIVATE +add_executable(MeshPart_tests_run MeshPart.cpp ) diff --git a/tests/src/Mod/MeshPart/CMakeLists.txt b/tests/src/Mod/MeshPart/CMakeLists.txt index 9b5cddaf2f..019097ca2c 100644 --- a/tests/src/Mod/MeshPart/CMakeLists.txt +++ b/tests/src/Mod/MeshPart/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(MeshPart_tests_run gtest_main ${Google_Tests_LIBS} MeshPart ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index e83ec0c794..958db78e7d 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Part_tests_run PRIVATE +add_executable(Part_tests_run Attacher.cpp AttachExtension.cpp BRepMesh.cpp diff --git a/tests/src/Mod/Part/CMakeLists.txt b/tests/src/Mod/Part/CMakeLists.txt index 82e7f5b12b..e5ca397145 100644 --- a/tests/src/Mod/Part/CMakeLists.txt +++ b/tests/src/Mod/Part/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Part_tests_run gtest_main ${Google_Tests_LIBS} Part ) - -add_subdirectory(App) diff --git a/tests/src/Mod/PartDesign/App/CMakeLists.txt b/tests/src/Mod/PartDesign/App/CMakeLists.txt index 8a33bc519e..9957f510f0 100644 --- a/tests/src/Mod/PartDesign/App/CMakeLists.txt +++ b/tests/src/Mod/PartDesign/App/CMakeLists.txt @@ -1,5 +1,5 @@ -target_sources(PartDesign_tests_run PRIVATE +add_executable(PartDesign_tests_run BackwardCompatibility.cpp DatumPlane.cpp ShapeBinder.cpp diff --git a/tests/src/Mod/PartDesign/CMakeLists.txt b/tests/src/Mod/PartDesign/CMakeLists.txt index a4b6aa366d..328b79e8c7 100644 --- a/tests/src/Mod/PartDesign/CMakeLists.txt +++ b/tests/src/Mod/PartDesign/CMakeLists.txt @@ -1,8 +1,8 @@ +add_subdirectory(App) + target_link_libraries(PartDesign_tests_run gtest_main ${Google_Tests_LIBS} PartDesign Sketcher ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Points/App/CMakeLists.txt b/tests/src/Mod/Points/App/CMakeLists.txt index f0edeee9fa..29a688a6bb 100644 --- a/tests/src/Mod/Points/App/CMakeLists.txt +++ b/tests/src/Mod/Points/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Points_tests_run PRIVATE +add_executable(Points_tests_run Points.cpp PointsFeature.cpp ) diff --git a/tests/src/Mod/Points/CMakeLists.txt b/tests/src/Mod/Points/CMakeLists.txt index 1620a16aae..a6cf99b6ed 100644 --- a/tests/src/Mod/Points/CMakeLists.txt +++ b/tests/src/Mod/Points/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Points_tests_run gtest_main ${Google_Tests_LIBS} Points ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Sketcher/App/CMakeLists.txt b/tests/src/Mod/Sketcher/App/CMakeLists.txt index 5c4d2660a5..4f0e1dd60d 100644 --- a/tests/src/Mod/Sketcher/App/CMakeLists.txt +++ b/tests/src/Mod/Sketcher/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Sketcher_tests_run PRIVATE +add_executable(Sketcher_tests_run SketcherTestHelpers.cpp SketchObject.cpp SketchObjectChanges.cpp diff --git a/tests/src/Mod/Sketcher/CMakeLists.txt b/tests/src/Mod/Sketcher/CMakeLists.txt index 38271f56c0..e034c91afb 100644 --- a/tests/src/Mod/Sketcher/CMakeLists.txt +++ b/tests/src/Mod/Sketcher/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Sketcher_tests_run gtest_main ${Google_Tests_LIBS} Sketcher ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Spreadsheet/App/CMakeLists.txt b/tests/src/Mod/Spreadsheet/App/CMakeLists.txt index 06fda257d3..c3f8330a34 100644 --- a/tests/src/Mod/Spreadsheet/App/CMakeLists.txt +++ b/tests/src/Mod/Spreadsheet/App/CMakeLists.txt @@ -1,4 +1,4 @@ -target_sources(Spreadsheet_tests_run PRIVATE +add_executable(Spreadsheet_tests_run PropertySheet.cpp RenameProperty.cpp ) diff --git a/tests/src/Mod/Spreadsheet/CMakeLists.txt b/tests/src/Mod/Spreadsheet/CMakeLists.txt index 4f46689b85..2bfcf5a276 100644 --- a/tests/src/Mod/Spreadsheet/CMakeLists.txt +++ b/tests/src/Mod/Spreadsheet/CMakeLists.txt @@ -1,7 +1,7 @@ +add_subdirectory(App) + target_link_libraries(Spreadsheet_tests_run gtest_main ${Google_Tests_LIBS} Spreadsheet ) - -add_subdirectory(App) diff --git a/tests/src/Mod/Start/App/CMakeLists.txt b/tests/src/Mod/Start/App/CMakeLists.txt index 4220b32b8e..1bc72689e7 100644 --- a/tests/src/Mod/Start/App/CMakeLists.txt +++ b/tests/src/Mod/Start/App/CMakeLists.txt @@ -1,8 +1,6 @@ -target_sources( - Start_tests_run - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/FileUtilities.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailSource.cpp +add_executable(Start_tests_run + FileUtilities.cpp + ThumbnailSource.cpp ) target_include_directories( diff --git a/tests/src/Mod/Start/CMakeLists.txt b/tests/src/Mod/Start/CMakeLists.txt index 62680aa490..24fe58429a 100644 --- a/tests/src/Mod/Start/CMakeLists.txt +++ b/tests/src/Mod/Start/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(App) target_include_directories(Start_tests_run PUBLIC ${Python3_INCLUDE_DIRS} @@ -8,5 +9,3 @@ target_link_libraries(Start_tests_run ${Google_Tests_LIBS} Start ) - -add_subdirectory(App) diff --git a/tests/src/zipios++/CMakeLists.txt b/tests/src/zipios++/CMakeLists.txt index 4d34c683da..cbf5ac19c5 100644 --- a/tests/src/zipios++/CMakeLists.txt +++ b/tests/src/zipios++/CMakeLists.txt @@ -1,6 +1,18 @@ -target_sources( - Tests_run - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/collectioncollection.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/zipfile.cpp +add_executable(Zipios_tests_run + collectioncollection.cpp + zipfile.cpp +) + + +target_link_libraries(Zipios_tests_run PRIVATE + GTest::gtest_main + GTest::gmock_main + ${Google_Tests_LIBS} + FreeCADApp +) + +target_include_directories( + Zipios_tests_run PRIVATE + ${ZIPIOS_INCLUDES} + ${ZLIB_INCLUDE_DIR} ) From f55590a0aeb6898f53e7a3c70e83d8865e24e89e Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 15 Jun 2025 10:47:14 -0500 Subject: [PATCH 4/4] CI: Update test names in runner --- .../runCPPTests/runAllTests/action.yml | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actions/runCPPTests/runAllTests/action.yml b/.github/workflows/actions/runCPPTests/runAllTests/action.yml index 7eaaab59ce..937b364e4a 100644 --- a/.github/workflows/actions/runCPPTests/runAllTests/action.yml +++ b/.github/workflows/actions/runCPPTests/runAllTests/action.yml @@ -47,13 +47,27 @@ runs: testCommand: ${{ inputs.builddir }}/tests/Assembly_tests_run --gtest_output=json:${{ inputs.reportdir }}assembly_gtest_results.json testLogFile: ${{ inputs.reportdir }}assembly_gtest_test_log.txt testName: Assembly - - name: C++ core tests - id: core + - name: C++ app tests + id: app uses: ./.github/workflows/actions/runCPPTests/runSingleTest with: - testCommand: ${{ inputs.builddir }}/tests/Tests_run --gtest_output=json:${{ inputs.reportdir }}core_gtest_results.json - testLogFile: ${{ inputs.reportdir }}core_gtest_test_log.txt - testName: Core + testCommand: ${{ inputs.builddir }}/tests/App_tests_run --gtest_output=json:${{ inputs.reportdir }}app_gtest_results.json + testLogFile: ${{ inputs.reportdir }}app_gtest_test_log.txt + testName: App + - name: C++ base tests + id: base + uses: ./.github/workflows/actions/runCPPTests/runSingleTest + with: + testCommand: ${{ inputs.builddir }}/tests/Base_tests_run --gtest_output=json:${{ inputs.reportdir }}base_gtest_results.json + testLogFile: ${{ inputs.reportdir }}base_gtest_test_log.txt + testName: Base + - name: C++ Gui tests + id: gui + uses: ./.github/workflows/actions/runCPPTests/runSingleTest + with: + testCommand: ${{ inputs.builddir }}/tests/Gui_tests_run --gtest_output=json:${{ inputs.reportdir }}gui_gtest_results.json + testLogFile: ${{ inputs.reportdir }}gui_gtest_test_log.txt + testName: Gui - name: C++ Material tests id: material uses: ./.github/workflows/actions/runCPPTests/runSingleTest @@ -82,6 +96,13 @@ runs: testCommand: ${{ inputs.builddir }}/tests/Mesh_tests_run --gtest_output=json:${{ inputs.reportdir }}meshpart_gtest_results.json testLogFile: ${{ inputs.reportdir }}meshpart_gtest_test_log.txt testName: MeshPart + - name: C++ Misc tests + id: misc + uses: ./.github/workflows/actions/runCPPTests/runSingleTest + with: + testCommand: ${{ inputs.builddir }}/tests/Misc_tests_run --gtest_output=json:${{ inputs.reportdir }}misc_gtest_results.json + testLogFile: ${{ inputs.reportdir }}misc_gtest_test_log.txt + testName: Misc - name: C++ Part tests id: part uses: ./.github/workflows/actions/runCPPTests/runSingleTest