mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-19 12:34:15 +02:00
Merge pull request #1257 from ispyisail/feature/pdf-reproducible
Add repeatable PDF export when SOURCE_DATE_EPOCH is set
This commit is contained in:
@@ -433,6 +433,19 @@ target_link_libraries(tst_potentialtextcarrier PRIVATE Qt::Test)
|
||||
target_compile_definitions(tst_potentialtextcarrier PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\"")
|
||||
|
||||
# With SOURCE_DATE_EPOCH set, --export-pdf writes the same bytes for the
|
||||
# same project in every run. Exports examples/741.qet several times, in
|
||||
# separate processes.
|
||||
add_executable(
|
||||
tst_pdfreproducible
|
||||
tst_pdfreproducible.cpp)
|
||||
add_test(NAME tst_pdfreproducible COMMAND tst_pdfreproducible)
|
||||
add_dependencies(tst_pdfreproducible qelectrotech)
|
||||
target_link_libraries(tst_pdfreproducible PRIVATE Qt::Test)
|
||||
target_compile_definitions(tst_pdfreproducible PRIVATE
|
||||
"QET_TEST_BINARY_PATH=\"$<TARGET_FILE:qelectrotech>\""
|
||||
"QET_EXAMPLES_DIR=\"${QET_DIR}/examples\"")
|
||||
|
||||
# Hops where wires cross (issue #436): the geometry in wirehops.cpp, and
|
||||
# through the real binary, that --resave keeps the project setting and
|
||||
# adds nothing to a project without it.
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
With SOURCE_DATE_EPOCH set, --export-pdf writes the same bytes for the
|
||||
same project in every run: the dates are the ones it names, the document
|
||||
id comes from the project file, and the fonts come in a fixed order.
|
||||
Without it the dates are the time of the export, as before. Exported in
|
||||
separate processes, since what used to differ changed between runs.
|
||||
*/
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
class tst_pdfreproducible : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QTemporaryDir m_dir;
|
||||
int m_run = 0;
|
||||
|
||||
QByteArray exportPdf(const QString &project, const QByteArray &epoch)
|
||||
{
|
||||
const int run = m_run++;
|
||||
const QString home = m_dir.filePath(QStringLiteral("home%1").arg(run));
|
||||
const QString tmp = m_dir.filePath(QStringLiteral("tmp%1").arg(run));
|
||||
const QString out = m_dir.filePath(QStringLiteral("out%1.pdf").arg(run));
|
||||
QDir().mkpath(home);
|
||||
QDir().mkpath(tmp);
|
||||
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
|
||||
env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("offscreen"));
|
||||
env.insert(QStringLiteral("HOME"), home);
|
||||
env.insert(QStringLiteral("XDG_CONFIG_HOME"), home + QStringLiteral("/.config"));
|
||||
env.insert(QStringLiteral("XDG_DATA_HOME"), home + QStringLiteral("/.local/share"));
|
||||
env.insert(QStringLiteral("TMPDIR"), tmp);
|
||||
env.remove(QStringLiteral("QT_HASH_SEED"));
|
||||
env.remove(QStringLiteral("SOURCE_DATE_EPOCH"));
|
||||
if (!epoch.isEmpty())
|
||||
env.insert(QStringLiteral("SOURCE_DATE_EPOCH"), QString::fromLatin1(epoch));
|
||||
|
||||
QProcess proc;
|
||||
proc.setProcessEnvironment(env);
|
||||
proc.start(QStringLiteral(QET_TEST_BINARY_PATH),
|
||||
{QStringLiteral("--export-pdf"), project, out});
|
||||
if (!proc.waitForFinished(120000) || proc.exitCode() != 0)
|
||||
return {};
|
||||
QFile file(out);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return {};
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
/// Every object the xref table lists starts where the table says: the
|
||||
/// dates are rewritten after Qt writes the file, which moves them.
|
||||
static bool xrefMatches(const QByteArray &pdf)
|
||||
{
|
||||
const int sx = pdf.lastIndexOf("startxref");
|
||||
if (sx == -1)
|
||||
return false;
|
||||
const int xref = pdf.mid(sx + 9).trimmed().split('\n').value(0).toInt();
|
||||
if (!pdf.mid(xref).startsWith("xref"))
|
||||
return false;
|
||||
const QList<QByteArray> lines = pdf.mid(xref).split('\n');
|
||||
const QList<QByteArray> header = lines.value(1).split(' ');
|
||||
const int first = header.value(0).toInt();
|
||||
const int count = header.value(1).toInt();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const QList<QByteArray> entry = lines.value(2 + i).split(' ');
|
||||
if (entry.value(2) != "n")
|
||||
continue;
|
||||
const QByteArray obj = QByteArray::number(first + i) + " 0 obj";
|
||||
if (pdf.mid(entry.value(0).toInt(), obj.size()) != obj)
|
||||
return false;
|
||||
}
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
private slots:
|
||||
void initTestCase()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QVERIFY(QFile::exists(QStringLiteral(QET_TEST_BINARY_PATH)));
|
||||
}
|
||||
|
||||
void sameBytesEveryRun()
|
||||
{
|
||||
// A project with no cross-reference links: their order is fixed
|
||||
// separately.
|
||||
const QString project =
|
||||
QStringLiteral(QET_EXAMPLES_DIR) + QStringLiteral("/741.qet");
|
||||
QVERIFY2(QFile::exists(project), "examples/741.qet not found");
|
||||
|
||||
const QByteArray first = exportPdf(project, "1700000000");
|
||||
QVERIFY(!first.isEmpty());
|
||||
QVERIFY(first.contains("/CreationDate (D:20231114221320Z)"));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
QVERIFY(first.contains("xmp:CreateDate=\"2023-11-14T22:13:20Z\""));
|
||||
#endif
|
||||
QVERIFY2(xrefMatches(first), "the xref table does not match the file");
|
||||
for (int i = 0; i < 3; ++i)
|
||||
QVERIFY2(exportPdf(project, "1700000000") == first,
|
||||
"two exports of the same project differ");
|
||||
}
|
||||
|
||||
void nowWithoutTheVariable()
|
||||
{
|
||||
const QString project =
|
||||
QStringLiteral(QET_EXAMPLES_DIR) + QStringLiteral("/741.qet");
|
||||
const QByteArray pdf = exportPdf(project, QByteArray());
|
||||
QVERIFY(!pdf.isEmpty());
|
||||
const QByteArray year =
|
||||
"/CreationDate (D:" + QByteArray::number(QDate::currentDate().year());
|
||||
QVERIFY(pdf.contains(year));
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(tst_pdfreproducible)
|
||||
#include "tst_pdfreproducible.moc"
|
||||
Reference in New Issue
Block a user