mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-18 12:04:13 +02:00
Merge master into feat/changed-on-disk-check
Conflicts: QETProject::write() now checks for a change on disk before it keeps the previous version; both new tests kept in tests/qttest/CMakeLists.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgPFANhWgD66V3v9KVKV58
This commit is contained in:
@@ -931,6 +931,30 @@ if(QET_HAS_SCRIPTING)
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# A folio report keeps showing where its partner is when another of its
|
||||
# information changes (F116), on examples/iso_sfc_example.qet.
|
||||
add_executable(
|
||||
tst_reporttextafterinfo
|
||||
tst_reporttextafterinfo.cpp)
|
||||
add_test(NAME tst_reporttextafterinfo COMMAND tst_reporttextafterinfo)
|
||||
add_dependencies(tst_reporttextafterinfo qelectrotech)
|
||||
target_link_libraries(tst_reporttextafterinfo PRIVATE Qt::Test Qt::Xml)
|
||||
target_compile_definitions(tst_reporttextafterinfo PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# A .qetz folio whose title only changed keeps its pictures when saved
|
||||
# with folios built when needed (F118), on examples/grafcet.qet.
|
||||
add_executable(
|
||||
tst_qetzheaderpictures
|
||||
tst_qetzheaderpictures.cpp)
|
||||
add_test(NAME tst_qetzheaderpictures COMMAND tst_qetzheaderpictures)
|
||||
add_dependencies(tst_qetzheaderpictures qelectrotech)
|
||||
target_link_libraries(tst_qetzheaderpictures PRIVATE Qt::Test)
|
||||
target_compile_definitions(tst_qetzheaderpictures PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# Saving refuses a project file another user or program changed since it was
|
||||
# opened (QETProject::changedOnDisk()); QET_OVERWRITE_CHANGED=1 overrides.
|
||||
add_executable(
|
||||
@@ -1276,6 +1300,17 @@ if(Qt6_VERSION VERSION_LESS 6.5.0)
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# The versions of a project kept before it is saved over: the newest
|
||||
# kept, same bytes not kept twice, a failed copy said
|
||||
# (sources/utils/previousversions.*).
|
||||
add_executable(
|
||||
tst_previousversions
|
||||
tst_previousversions.cpp
|
||||
${QET_DIR}/sources/utils/previousversions.cpp)
|
||||
add_test(NAME tst_previousversions COMMAND tst_previousversions)
|
||||
target_include_directories(tst_previousversions PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_previousversions PRIVATE Qt::Test)
|
||||
|
||||
# The zip a .qetz project is stored in: round trip, same bytes twice,
|
||||
# unsafe entry names refused (sources/container/qetzip.*, bundled miniz).
|
||||
add_executable(
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
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 <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
#include "utils/previousversions.h"
|
||||
|
||||
// The versions of a project kept before it is saved over
|
||||
// (sources/utils/previousversions.*)
|
||||
class tst_previousversions : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
|
||||
// @p path holding @p text, saved at @p when
|
||||
static void write(const QString &path, const QByteArray &text, const QDateTime &when)
|
||||
{
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Truncate));
|
||||
file.write(text);
|
||||
file.flush(); //else closing writes, and sets the time to now
|
||||
QVERIFY(file.setFileTime(when, QFileDevice::FileModificationTime));
|
||||
}
|
||||
|
||||
static QByteArray read(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase() { QVERIFY(m_dir.isValid()); }
|
||||
|
||||
// Each save keeps the file as it was, newest first, named by the time
|
||||
// that version was saved, in "<name>-backups" beside it; only the
|
||||
// newest are kept
|
||||
void keepsTheNewest()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("plant.qetz"));
|
||||
const QDateTime start(QDate(2026, 10, 16), QTime(8, 0, 0));
|
||||
for (int i = 0 ; i < 7 ; ++i) {
|
||||
write(project, "version " + QByteArray::number(i), start.addSecs(60 * i));
|
||||
QVERIFY(PreviousVersions::keep(project, 3));
|
||||
}
|
||||
const QStringList kept = PreviousVersions::kept(project);
|
||||
QCOMPARE(kept.size(), 3);
|
||||
QCOMPARE(read(kept.at(0)), QByteArray("version 6"));
|
||||
QCOMPARE(read(kept.at(2)), QByteArray("version 4"));
|
||||
QCOMPARE(QFileInfo(kept.at(0)).fileName(), QStringLiteral("plant 2026-10-16 08-06-00.qetz"));
|
||||
QCOMPARE(QFileInfo(kept.at(0)).absolutePath(), m_dir.filePath(QStringLiteral("plant-backups")));
|
||||
// The project itself is untouched
|
||||
QCOMPARE(read(project), QByteArray("version 6"));
|
||||
}
|
||||
|
||||
// Saving the same bytes again keeps no second copy
|
||||
void sameBytesNotKeptTwice()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("same.qet"));
|
||||
const QDateTime when(QDate(2026, 10, 16), QTime(9, 0, 0));
|
||||
write(project, "unchanged", when);
|
||||
QVERIFY(PreviousVersions::keep(project, 5));
|
||||
write(project, "unchanged", when.addSecs(30));
|
||||
QVERIFY(PreviousVersions::keep(project, 5));
|
||||
QCOMPARE(PreviousVersions::kept(project).size(), 1);
|
||||
}
|
||||
|
||||
// Two versions saved the same second both kept
|
||||
void sameSecond()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("fast.qetz"));
|
||||
const QDateTime when(QDate(2026, 10, 16), QTime(10, 0, 0));
|
||||
write(project, "first", when);
|
||||
QVERIFY(PreviousVersions::keep(project, 5));
|
||||
write(project, "second", when);
|
||||
QVERIFY(PreviousVersions::keep(project, 5));
|
||||
QCOMPARE(PreviousVersions::kept(project).size(), 2);
|
||||
}
|
||||
|
||||
// Nothing kept for a new file, or when the count is 0; another
|
||||
// project's versions in the same folder are not counted
|
||||
void nothingToKeep()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("new.qetz"));
|
||||
QVERIFY(PreviousVersions::keep(project, 5));
|
||||
QVERIFY(!QFileInfo::exists(PreviousVersions::folder(project)));
|
||||
write(project, "x", QDateTime(QDate(2026, 10, 16), QTime(11, 0, 0)));
|
||||
QVERIFY(PreviousVersions::keep(project, 0));
|
||||
QVERIFY(!QFileInfo::exists(PreviousVersions::folder(project)));
|
||||
QVERIFY(PreviousVersions::kept(m_dir.filePath(QStringLiteral("other.qetz"))).isEmpty());
|
||||
}
|
||||
|
||||
// A copy that cannot be made says so, and leaves the project as it is
|
||||
void copyFails()
|
||||
{
|
||||
const QString project = m_dir.filePath(QStringLiteral("blocked.qetz"));
|
||||
write(project, "data", QDateTime(QDate(2026, 10, 16), QTime(12, 0, 0)));
|
||||
// A file where the folder should be
|
||||
write(PreviousVersions::folder(project), "", QDateTime::currentDateTime());
|
||||
QString error;
|
||||
QVERIFY(!PreviousVersions::keep(project, 5, &error));
|
||||
QVERIFY(!error.isEmpty());
|
||||
QCOMPARE(read(project), QByteArray("data"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(tst_previousversions)
|
||||
|
||||
#include "tst_previousversions.moc"
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QtEndian>
|
||||
|
||||
#include <functional>
|
||||
|
||||
@@ -533,6 +534,38 @@ private slots:
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
|
||||
// A page of the database overwritten, as a bad disk or a broken copy
|
||||
// would: refused, and said to be damaged (quick_check on opening),
|
||||
// whichever table the page held
|
||||
void damagedDatabaseRefused_data()
|
||||
{
|
||||
QTest::addColumn<double>("where");
|
||||
QTest::newRow("second page") << 0.0;
|
||||
QTest::newRow("middle page") << 0.5;
|
||||
QTest::newRow("last page") << 1.0;
|
||||
}
|
||||
void damagedDatabaseRefused()
|
||||
{
|
||||
QFETCH(double, where);
|
||||
QDomDocument original;
|
||||
QList<QetZip::Entry> entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original);
|
||||
bool damaged = false;
|
||||
for (QetZip::Entry &entry : entries) {
|
||||
if (entry.name != QLatin1String("project.sqlite")) continue;
|
||||
const int page_size = qFromBigEndian<quint16>(entry.data.constData() + 16);
|
||||
const int pages = int(entry.data.size() / page_size);
|
||||
QVERIFY(pages > 3);
|
||||
const int page = 1 + int(where * (pages - 2));
|
||||
entry.data.replace(page * page_size, page_size, QByteArray(page_size, char(0xff)));
|
||||
damaged = true;
|
||||
}
|
||||
QVERIFY(damaged);
|
||||
QDomDocument back;
|
||||
QString error;
|
||||
QVERIFY(!QetContainer::join(entries, &back, &error));
|
||||
QVERIFY2(error.contains(QStringLiteral("damaged")), qPrintable(error));
|
||||
}
|
||||
|
||||
// project.sqlite changed by @p sql in @p entries
|
||||
static void alterDatabase(QList<QetZip::Entry> &entries, const char *sql)
|
||||
{
|
||||
@@ -764,6 +797,119 @@ private slots:
|
||||
QString error;
|
||||
QVERIFY(!QetContainer::join({{QStringLiteral("mimetype"), "application/zip", false}}, &back, &error));
|
||||
}
|
||||
|
||||
// recover() reads what it can of a damaged .qetz: each kind of damage
|
||||
// costs what it held and nothing else, and is said
|
||||
void recoverLeavesOutOnlyTheDamage_data()
|
||||
{
|
||||
QTest::addColumn<QString>("damage");
|
||||
QTest::addColumn<QString>("said");
|
||||
QTest::addColumn<int>("folios");
|
||||
QTest::addColumn<int>("wires");
|
||||
QTest::newRow("nothing") << QString() << QString() << 0 << 0;
|
||||
QTest::newRow("folio file missing") << QStringLiteral("folio") << QStringLiteral("is missing") << -1 << 0;
|
||||
QTest::newRow("wire row deleted") << QStringLiteral("row") << QStringLiteral("not in the database") << 0 << -1;
|
||||
QTest::newRow("database page overwritten") << QStringLiteral("page") << QStringLiteral("cannot be read") << 0 << -1000;
|
||||
}
|
||||
void recoverLeavesOutOnlyTheDamage()
|
||||
{
|
||||
QFETCH(QString, damage);
|
||||
QFETCH(QString, said);
|
||||
QFETCH(int, folios);
|
||||
QFETCH(int, wires);
|
||||
QDomDocument original;
|
||||
QList<QetZip::Entry> entries = splitFile(QStringLiteral(QET_EXAMPLES_DIR "/industrial.qet"), &original);
|
||||
QVERIFY(!entries.isEmpty());
|
||||
auto count = [](const QDomDocument &d, const QString &tag) {
|
||||
return int(d.documentElement().elementsByTagName(tag).size());
|
||||
};
|
||||
const int folios_before = count(original, QStringLiteral("diagram"));
|
||||
const int wires_before = count(original, QStringLiteral("conductor"));
|
||||
if (damage == QLatin1String("folio")) {
|
||||
for (int i = 0 ; i < entries.size() ; ++i)
|
||||
if (entries.at(i).name.startsWith(QStringLiteral("folios/"))) { entries.removeAt(i); break; }
|
||||
} else if (damage == QLatin1String("row")) {
|
||||
alterDatabase(entries, "DELETE FROM conductor WHERE rowid = (SELECT min(rowid) FROM conductor)");
|
||||
} else if (damage == QLatin1String("page")) {
|
||||
for (QetZip::Entry &entry : entries) {
|
||||
if (entry.name != QLatin1String("project.sqlite")) continue;
|
||||
const int page_size = qFromBigEndian<quint16>(entry.data.constData() + 16);
|
||||
const int page = int(entry.data.size() / page_size / 2);
|
||||
entry.data.replace(page * page_size, page_size, QByteArray(page_size, char(0xff)));
|
||||
}
|
||||
}
|
||||
QByteArray zip;
|
||||
QString error;
|
||||
QVERIFY2(QetZip::toBytes(entries, &error).size() > 0, qPrintable(error));
|
||||
zip = QetZip::toBytes(entries, &error);
|
||||
|
||||
QDomDocument back;
|
||||
QStringList lost;
|
||||
QVERIFY2(QetContainer::recover(zip, &back, &lost, &error), qPrintable(error));
|
||||
if (said.isEmpty()) {
|
||||
QVERIFY2(lost.isEmpty(), qPrintable(lost.join(QLatin1Char('\n'))));
|
||||
} else {
|
||||
QVERIFY2(lost.join(QLatin1Char('\n')).contains(said), qPrintable(lost.join(QLatin1Char('\n'))));
|
||||
// Strict reading refuses it
|
||||
QDomDocument strict;
|
||||
QVERIFY(!QetContainer::join(entries, &strict));
|
||||
}
|
||||
QCOMPARE(count(back, QStringLiteral("diagram")), folios_before + folios);
|
||||
if (wires == -1000)
|
||||
QCOMPARE(count(back, QStringLiteral("conductor")), 0);
|
||||
else
|
||||
QCOMPARE(count(back, QStringLiteral("conductor")), wires_before + wires);
|
||||
// Nothing of the container's own left in the document
|
||||
QVERIFY(!back.toString().contains(QStringLiteral("qetz-")));
|
||||
}
|
||||
|
||||
// A picture's file missing (F118 wrote such projects): the picture is
|
||||
// left out, the rest read
|
||||
void recoverMissingPicture()
|
||||
{
|
||||
QList<QetZip::Entry> entries;
|
||||
QString error;
|
||||
QVERIFY2(QetZip::fromBytes(read(QStringLiteral(QET_FIXTURES_DIR "/qetz_format2.qetz")), &entries, &error),
|
||||
qPrintable(error));
|
||||
QDomDocument document;
|
||||
QVERIFY2(QetContainer::join(entries, &document, &error), qPrintable(error));
|
||||
// A picture on the first folio, then saved and its file dropped
|
||||
QDomElement folio = document.documentElement().firstChildElement(QStringLiteral("diagram"));
|
||||
QDomElement images = folio.firstChildElement(QStringLiteral("images"));
|
||||
if (images.isNull()) images = folio.appendChild(document.createElement(QStringLiteral("images"))).toElement();
|
||||
QDomElement image = document.createElement(QStringLiteral("image"));
|
||||
image.setAttribute(QStringLiteral("type"), QStringLiteral("png"));
|
||||
image.appendChild(document.createTextNode(QStringLiteral(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==")));
|
||||
images.appendChild(image);
|
||||
QList<QetZip::Entry> saved = QetContainer::split(document, QString(), &error);
|
||||
QVERIFY2(!saved.isEmpty(), qPrintable(error));
|
||||
bool dropped = false;
|
||||
for (int i = 0 ; i < saved.size() ; ++i)
|
||||
if (saved.at(i).name.startsWith(QStringLiteral("images/"))) { saved.removeAt(i); dropped = true; break; }
|
||||
QVERIFY(dropped);
|
||||
QDomDocument strict;
|
||||
QVERIFY(!QetContainer::join(saved, &strict, &error));
|
||||
QVERIFY(error.contains(QStringLiteral("picture")));
|
||||
|
||||
QDomDocument back;
|
||||
QStringList lost;
|
||||
QVERIFY2(QetContainer::recover(QetZip::toBytes(saved), &back, &lost, &error), qPrintable(error));
|
||||
QCOMPARE(lost.size(), 1);
|
||||
QVERIFY(lost.first().contains(QStringLiteral("picture")));
|
||||
QCOMPARE(int(back.documentElement().elementsByTagName(QStringLiteral("image")).size()), 0);
|
||||
QVERIFY(back.toString().contains(QStringLiteral("<elementInformation ")));
|
||||
}
|
||||
|
||||
// What cannot be read as a zip at all is still refused
|
||||
void recoverNotAZipRefused()
|
||||
{
|
||||
QDomDocument back;
|
||||
QStringList lost;
|
||||
QString error;
|
||||
QVERIFY(!QetContainer::recover(QByteArray("not a zip").repeated(10), &back, &lost, &error));
|
||||
QVERIFY(!error.isEmpty());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_qetcontainer)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
// A folio of a .qetz whose title only changed is saved from its items as
|
||||
// read, not built (Diagram::deferredXmlWithHeader()). Those items name
|
||||
// their pictures' files in the .qetz, which the new .qetz left out: the
|
||||
// project saved then refused to open, 'picture "images/..." is missing'
|
||||
// (FINDINGS F118). Runs scripts through the real binary's --run on a copy
|
||||
// of examples/grafcet.qet given a picture on its second folio.
|
||||
class tst_qetzheaderpictures : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
|
||||
// Run @p script on @p project; its output
|
||||
QString run(const QString &script, const QString &project, bool every_folio_built)
|
||||
{
|
||||
const QString path = m_dir.filePath(QStringLiteral("script.js"));
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return QString();
|
||||
f.write(script.toUtf8());
|
||||
f.close();
|
||||
const QString home = m_dir.filePath(QStringLiteral("home"));
|
||||
QDir().mkpath(home);
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
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());
|
||||
if (every_folio_built)
|
||||
env.insert(QStringLiteral("QET_EAGER_FOLIOS"), QStringLiteral("1"));
|
||||
else
|
||||
env.remove(QStringLiteral("QET_EAGER_FOLIOS"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH), {QStringLiteral("--run"), path, project});
|
||||
proc.waitForFinished(120000);
|
||||
return QString::fromUtf8(proc.readAllStandardOutput() + proc.readAllStandardError());
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
}
|
||||
|
||||
void titleChangeKeepsPictures()
|
||||
{
|
||||
// A 1x1 PNG
|
||||
const QString png = m_dir.filePath(QStringLiteral("dot.png"));
|
||||
QFile p(png);
|
||||
QVERIFY(p.open(QIODevice::WriteOnly));
|
||||
p.write(QByteArray::fromBase64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="));
|
||||
p.close();
|
||||
const QString with_picture = m_dir.filePath(QStringLiteral("picture.qetz"));
|
||||
const QString titled = m_dir.filePath(QStringLiteral("titled.qetz"));
|
||||
QString out = run(QStringLiteral("qet.log('ADDED ' + qet.addImage(1, '%1', 100, 100));"
|
||||
"qet.save('%2');").arg(png, with_picture),
|
||||
QStringLiteral(QET_EXAMPLES_DIR "/grafcet.qet"), true);
|
||||
QVERIFY2(out.contains(QStringLiteral("ADDED 0")), qPrintable(out));
|
||||
QVERIFY(QFile::exists(with_picture));
|
||||
|
||||
// Only the title of the folio with the picture, folios built
|
||||
// when needed
|
||||
out = run(QStringLiteral("qet.log('SET ' + qet.setFolioProperty(1, 'title', 'Edited'));"
|
||||
"qet.save('%1');").arg(titled), with_picture, false);
|
||||
QVERIFY2(out.contains(QStringLiteral("SET true")), qPrintable(out));
|
||||
QVERIFY(QFile::exists(titled));
|
||||
|
||||
// Opens, with its picture and the new title
|
||||
out = run(QStringLiteral("qet.log('PICTURES ' + qet.images(1).length);"
|
||||
"qet.log('TITLE ' + qet.folioTitle(1));"), titled, true);
|
||||
QVERIFY2(out.contains(QStringLiteral("PICTURES 1")), qPrintable(out));
|
||||
QVERIFY2(out.contains(QStringLiteral("TITLE Edited")), qPrintable(out));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_qetzheaderpictures)
|
||||
|
||||
#include "tst_qetzheaderpictures.moc"
|
||||
@@ -0,0 +1,106 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <QtTest>
|
||||
|
||||
#include <QDir>
|
||||
#include <QDomDocument>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryDir>
|
||||
|
||||
// A folio report shows where its partner is. Changing any other
|
||||
// information of the report wrote back the label stored in its
|
||||
// information, the partner's position when the file was last saved, so
|
||||
// the report pointed at the wrong place until the partner moved again
|
||||
// (FINDINGS F116). Runs a script through the real binary's --run on
|
||||
// examples/iso_sfc_example.qet: the "jump to" of folio 1 moves from
|
||||
// column 4 to column 9, then the "jump from" on folio 2 gets a comment.
|
||||
class tst_reporttextafterinfo : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
|
||||
// The text a symbol @p uuid of @p saved shows for its label
|
||||
static QString labelText(const QString &saved, const QString &uuid)
|
||||
{
|
||||
QFile file(saved);
|
||||
if (!file.open(QIODevice::ReadOnly)) return QStringLiteral("(not saved)");
|
||||
QDomDocument doc;
|
||||
if (!doc.setContent(&file)) return QStringLiteral("(not read)");
|
||||
const QDomNodeList elements = doc.elementsByTagName(QStringLiteral("element"));
|
||||
for (int i = 0 ; i < elements.size() ; ++i) {
|
||||
const QDomElement e = elements.at(i).toElement();
|
||||
if (e.attribute(QStringLiteral("uuid")) != uuid) continue;
|
||||
const QDomNodeList texts = e.elementsByTagName(QStringLiteral("dynamic_elmt_text"));
|
||||
for (int j = 0 ; j < texts.size() ; ++j) {
|
||||
const QDomElement t = texts.at(j).toElement();
|
||||
if (t.firstChildElement(QStringLiteral("info_name")).text() == QLatin1String("label"))
|
||||
return t.firstChildElement(QStringLiteral("text")).text();
|
||||
}
|
||||
}
|
||||
return QStringLiteral("(no such text)");
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
}
|
||||
|
||||
void reportFollowsItsPartner_data()
|
||||
{
|
||||
QTest::addColumn<bool>("every_folio_built");
|
||||
QTest::newRow("folios built when needed") << false;
|
||||
QTest::newRow("every folio built") << true;
|
||||
}
|
||||
void reportFollowsItsPartner()
|
||||
{
|
||||
QFETCH(bool, every_folio_built);
|
||||
const QString tag = every_folio_built ? QStringLiteral("eager") : QStringLiteral("lazy");
|
||||
const QString saved = m_dir.filePath(tag + QStringLiteral(".qet"));
|
||||
const QString script = m_dir.filePath(tag + QStringLiteral(".js"));
|
||||
QFile f(script);
|
||||
QVERIFY(f.open(QIODevice::WriteOnly));
|
||||
f.write("qet.moveElement(0, '{bcf3d2e9-7190-48ed-bbb5-57bcc9ba8cfa}', 300, 0);\n"
|
||||
"qet.setElementInfo(1, '{016d0266-33ac-4255-8fe2-4d9629a12d86}', 'comment', 'edited');\n"
|
||||
"qet.save('" + saved.toUtf8() + "');\n");
|
||||
f.close();
|
||||
|
||||
// Columns numbered from 1, as QElectroTech's settings page
|
||||
// shows by default
|
||||
const QString home = m_dir.filePath(tag + QStringLiteral("-home"));
|
||||
QVERIFY(QDir().mkpath(home + QStringLiteral("/config/QElectroTech")));
|
||||
QFile conf(home + QStringLiteral("/config/QElectroTech/QElectroTech.conf"));
|
||||
QVERIFY(conf.open(QIODevice::WriteOnly));
|
||||
conf.write("[General]\nborder-columns_0=false\n");
|
||||
conf.close();
|
||||
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("QET_ENABLE_SCRIPTING"), QStringLiteral("1"));
|
||||
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());
|
||||
if (every_folio_built)
|
||||
env.insert(QStringLiteral("QET_EAGER_FOLIOS"), QStringLiteral("1"));
|
||||
else
|
||||
env.remove(QStringLiteral("QET_EAGER_FOLIOS"));
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
|
||||
{QStringLiteral("--run"), script,
|
||||
QStringLiteral(QET_EXAMPLES_DIR "/iso_sfc_example.qet")});
|
||||
QVERIFY(proc.waitForFinished(120000));
|
||||
|
||||
// The partner, moved, and the report that shows where it is
|
||||
QCOMPARE(labelText(saved, QStringLiteral("{016d0266-33ac-4255-8fe2-4d9629a12d86}")),
|
||||
QStringLiteral("1-C9"));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_reporttextafterinfo)
|
||||
|
||||
#include "tst_reporttextafterinfo.moc"
|
||||
Reference in New Issue
Block a user