/* 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 . */ #include #include #include #include #include /** 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 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 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 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"