Merge pull request #1520 from ispyisail/fix/stable-saved-stamps
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m52s

Fix the saved date and time lagging one save in the title block
This commit is contained in:
ispyisail
2026-10-18 13:33:34 +13:00
committed by GitHub
4 changed files with 210 additions and 23 deletions
+50 -23
View File
@@ -474,6 +474,8 @@ QETProject::ProjectState QETProject::openFile(QFile *file)
}
QFileInfo fi(*file);
setFilePath(fi.absoluteFilePath());
//Saved again unchanged, the file keeps its saved date and path
m_stamped_path = m_file_path;
//Extract the content of the xml. The raw bytes are kept : a project
//file without a persisted uuid derives its uuid from them.
@@ -1161,16 +1163,8 @@ void QETProject::setFilePath(const QString &filepath)
setReadOnly(false);
}
//title block variables should be updated after file save as dialog is confirmed, before file is saved.
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
m_project_properties.addValue("savedfilepath", filePath());
//The saved date, time and path of the title block are set by
//write(), when the project is saved (stampSaved())
emit projectFilePathChanged(this, m_file_path);
emit projectInformationsChanged(this);
updateDiagramsFolioData();
@@ -2801,6 +2795,33 @@ bool QETProject::changedOnDisk(QDateTime *when) const
return true;
}
/**
@brief QETProject::stampSaved
Set the title block variables of the save (saveddate, saveddate-eu,
saveddate-us, savedtime, savedfilename, savedfilepath) to now and to
the file being written, when there is something new to save: the
project changed since it was opened or last saved, or it is written to
another file. Otherwise they keep what the file holds, so saving an
unchanged project gives an identical file (F124).
@return true if they were set
*/
bool QETProject::stampSaved()
{
const bool changed = m_modified || m_undo_stack->index() != m_stamped_undo_index;
if (!changed && m_file_path == m_stamped_path) {
return false;
}
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
m_project_properties.addValue("savedfilepath", filePath());
emit projectInformationsChanged(this);
updateDiagramsFolioData();
return true;
}
/**
@brief QETProject::write
Save the project in a file.
@@ -2868,13 +2889,28 @@ QETResult QETProject::write(bool overwrite_changed)
if (!PreviousVersions::keep(m_file_path, PreviousVersions::count(), &backup_error))
qWarning().noquote() << "Previous version of" << m_file_path << "not kept:" << backup_error;
//The saved date, time and path go into the file being written, and
//only when there is something new to save: an unchanged project
//saves to an identical file (F124, a project kept in git). A
//failed write puts the previous values back.
const DiagramContext stamped_before = m_project_properties;
const bool stamped = stampSaved();
const auto failed_stamped = [&](const QString &error) {
if (stamped) {
m_project_properties = stamped_before;
emit projectInformationsChanged(this);
updateDiagramsFolioData();
}
return failed(error);
};
QString error_message;
if (isContainerPath(m_file_path))
{
//A container is written from the whole document: every folio
//built (stage D4 writes it from the database)
if (!writeContainerFile(m_file_path, &error_message))
return(failed(error_message));
return(failed_stamped(error_message));
}
else
{
@@ -2884,7 +2920,7 @@ QETResult QETProject::write(bool overwrite_changed)
QDomDocument xml_project(toXml(&kept_folios));
if (!QET::writeXmlText(spliceKeptFolios(xml_project.toString(4), kept_folios),
m_file_path, &error_message))
return(failed(error_message));
return(failed_stamped(error_message));
}
rememberDiskState();
@@ -2905,17 +2941,8 @@ QETResult QETProject::write(bool overwrite_changed)
}
}
//title block variables should be updated after file save dialog is confirmed, before file is saved.
m_project_properties.addValue("saveddate", QLocale::system().toString(QDate::currentDate(), QLocale::ShortFormat));
m_project_properties.addValue("saveddate-us", QDate::currentDate().toString("yyyy-MM-dd"));
m_project_properties.addValue("saveddate-eu", QDate::currentDate().toString("dd-MM-yyyy"));
m_project_properties.addValue("savedtime", QDateTime::currentDateTime().toString("HH:mm"));
m_project_properties.addValue("savedfilename", QFileInfo(filePath()).completeBaseName());
m_project_properties.addValue("savedfilepath", filePath());
emit projectInformationsChanged(this);
updateDiagramsFolioData();
m_stamped_path = m_file_path;
m_stamped_undo_index = m_undo_stack->index();
setModified(false);
return(QETResult());
}
+5
View File
@@ -441,6 +441,7 @@ class QETProject : public QObject
}
private:
bool stampSaved();
void readProjectXml(QDomDocument &xml_project);
void readDiagramsXml(QDomDocument &xml_project);
void readElementsCollectionXml(QDomDocument &xml_project);
@@ -570,6 +571,10 @@ class QETProject : public QObject
bool m_kept_folios_trusted = false;
/// Read from a .qet whose path nobody chose since: saved beside it
bool m_opened_from_qet = false;
/// The file the saved date and path were set for, and the undo
/// index then: an unchanged project keeps them (stampSaved())
QString m_stamped_path;
int m_stamped_undo_index = 0;
/// The project file as this QET last read or wrote it, to see
/// whether something else changed it since (changedOnDisk())
QString m_disk_path;
+12
View File
@@ -889,6 +889,18 @@ if(QET_HAS_SCRIPTING)
target_compile_definitions(tst_scriptconductoruuid PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# The saved date, time and path of the title block change only when the
# project changed, and go into the file that save writes (F124): an
# unchanged project saved twice gives an identical file.
add_executable(
tst_savedstamps
tst_savedstamps.cpp)
add_test(NAME tst_savedstamps COMMAND tst_savedstamps)
add_dependencies(tst_savedstamps qelectrotech)
target_link_libraries(tst_savedstamps PRIVATE Qt::Test Qt::Xml)
target_compile_definitions(tst_savedstamps PRIVATE
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
# qet.terminalPosition() and qet.conductorPath(), checked against each
# other on a fixture's conductors.
add_executable(
+143
View File
@@ -0,0 +1,143 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtTest>
#include <QDate>
#include <QDomDocument>
#include <QProcess>
#include <QTemporaryDir>
/**
The title block variables of a save (saveddate, savedtime,
savedfilename, savedfilepath...) are set when the project is saved
with something new in it, and go into the file that save writes. They
used to be set after writing, so each save wrote the time of the one
before: a project saved twice without a change gave two different
files, and a changed project was saved with the previous date.
Runs the real binary on fixtures/unlinked_contact_label.qet, whose
stamps say 2026-10-02 18:39 in a temporary folder:
- saved twice in its own file without a change: byte-identical;
- one symbol moved, then saved: today's date and the file's own path.
*/
class tst_savedstamps : public QObject
{
Q_OBJECT
QTemporaryDir m_dir;
QString m_base;
QString run(const QStringList &arguments)
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
//Its own settings and single-instance socket: another test's
//QElectroTech running at the same time would otherwise be
//handed this run (SingleApplication)
const QString home = m_dir.filePath(QStringLiteral("home"));
QDir().mkpath(home);
env.insert(QStringLiteral("HOME"), home);
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/config"));
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/data"));
env.insert(QStringLiteral("TMPDIR"), m_dir.path());
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
QProcess proc;
proc.setProcessEnvironment(env);
proc.setProcessChannelMode(QProcess::MergedChannels);
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), arguments);
if (!proc.waitForFinished(120000) || proc.exitStatus() != QProcess::NormalExit
|| proc.exitCode() != 0)
return QString();
return QString::fromUtf8(proc.readAll()) + QStringLiteral("\nEXIT 0");
}
QString script(const QString &name, const QString &body)
{
const QString path = m_dir.filePath(name);
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) return QString();
file.write(body.toUtf8());
return path;
}
/// The saved-* properties of @p qetz, read back through --resave
QMap<QString, QString> stamps(const QString &qetz)
{
const QString xml = qetz + QStringLiteral(".qet");
if (run({QStringLiteral("--resave"), qetz, xml}).isEmpty()) return {};
QFile file(xml);
QDomDocument doc;
if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) return {};
QMap<QString, QString> out;
const QDomNodeList props = doc.documentElement().firstChildElement(
QStringLiteral("properties")).elementsByTagName(QStringLiteral("property"));
for (int i = 0; i < props.size(); ++i) {
const QDomElement p = props.at(i).toElement();
if (p.attribute(QStringLiteral("name")).startsWith(QLatin1String("saved")))
out.insert(p.attribute(QStringLiteral("name")), p.text());
}
return out;
}
static QByteArray bytes(const QString &path)
{
QFile file(path);
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
}
private slots:
void initTestCase()
{
QVERIFY2(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)), "qelectrotech binary not found");
QVERIFY(m_dir.isValid());
const QString fixture = QFINDTESTDATA("fixtures/unlinked_contact_label.qet");
QVERIFY2(!fixture.isEmpty(), "fixture project not found");
//The fixture as a .qetz, its stamps as they were: a copy
//written by --resave is not a save of the project
m_base = m_dir.filePath(QStringLiteral("base.qetz"));
QVERIFY(!run({QStringLiteral("--resave"), fixture, m_base}).isEmpty());
QCOMPARE(stamps(m_base).value(QStringLiteral("saveddate-us")), QStringLiteral("2026-10-02"));
}
void unchangedSavesAreIdentical()
{
const QString project = m_dir.filePath(QStringLiteral("unchanged.qetz"));
QVERIFY(QFile::copy(m_base, project));
const QString js = script(QStringLiteral("unchanged.js"),
QStringLiteral("qet.save('');\nqet.save('');\nqet.log('SAVED twice');\n"));
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("SAVED twice")));
QCOMPARE(bytes(project), bytes(m_base));
}
void changedSaveIsStampedNow()
{
const QString project = m_dir.filePath(QStringLiteral("changed.qetz"));
QVERIFY(QFile::copy(m_base, project));
const QString js = script(QStringLiteral("changed.js"), QStringLiteral(
"var u = qet.elementUuids(0)[0];\n"
"qet.log('MOVED ' + qet.moveElement(0, u, 10, 0));\n"
"qet.save('');\n"));
QVERIFY(run({QStringLiteral("--run"), js, project}).contains(QLatin1String("MOVED true")));
const QMap<QString, QString> s = stamps(project);
QCOMPARE(s.value(QStringLiteral("saveddate-us")), QDate::currentDate().toString(QStringLiteral("yyyy-MM-dd")));
QCOMPARE(s.value(QStringLiteral("savedfilepath")), QFileInfo(project).absoluteFilePath());
QCOMPARE(s.value(QStringLiteral("savedfilename")), QStringLiteral("changed"));
}
};
QTEST_GUILESS_MAIN(tst_savedstamps)
#include "tst_savedstamps.moc"