summaryrefslogtreecommitdiffstats
path: root/tests/test_mailsync.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_mailsync.cpp')
-rw-r--r--tests/test_mailsync.cpp177
1 files changed, 176 insertions, 1 deletions
diff --git a/tests/test_mailsync.cpp b/tests/test_mailsync.cpp
index e91de3e..45c7767 100644
--- a/tests/test_mailsync.cpp
+++ b/tests/test_mailsync.cpp
@@ -16,8 +16,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include <QElapsedTimer>
#include <QFile>
+#include <QProcess>
#include <QSignalSpy>
#include <QTemporaryDir>
#include <QtTest>
@@ -56,6 +56,15 @@ private slots:
void theChannelNameIsShown();
void aChannelNameIsNotLetInVerbatim();
+ void lastRunOutcomeReadsAnOkRun();
+ void lastRunOutcomeReadsAFailedRun();
+ void lastRunOutcomeTakesTheLastMarkerNotTheFirst();
+ void lastRunOutcomeOnAMissingLogIsUnknown();
+ void lastRunOutcomeOnALogWithNoMarkerIsUnknown();
+ void lastRunOutcomeIgnoresATrailingPartialRun();
+ void lastRunOutcomeReadsATailOfAHugeLog();
+ void lastRunOutcomeReadsABannerTheScriptActuallyWrote();
+
private:
/// Writes an executable shell script into the temp dir, returns its path.
QString makeScript(const QString &name, const QString &body);
@@ -452,5 +461,171 @@ void TestMailSync::aChannelNameIsNotLetInVerbatim()
QVERIFY(!text.contains(QLatin1Char('\n')));
}
+// Item 54. A cron sync clears the pending-edit count only if it succeeded, and
+// the only evidence of that available to this process is the RUN END line the
+// script writes into its log. These tests pin the parser against the exact
+// shape assets/mailsync.sh emits.
+
+void TestMailSync::lastRunOutcomeReadsAnOkRun()
+{
+ const QString path = m_dir.filePath(QStringLiteral("ok.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("===== RUN START: 2026-08-09T10:20:00+02:00 =====\n"
+ "10:20:01 Channel one\n"
+ "===== RUN END: 2026-08-09T10:20:03+02:00 status=OK =====\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Ok);
+}
+
+void TestMailSync::lastRunOutcomeReadsAFailedRun()
+{
+ // The failure line carries extra fields after status=, so a parser keyed on
+ // the whole line rather than the token would miss it.
+ const QString path = m_dir.filePath(QStringLiteral("failed.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("===== RUN END: 2026-08-09T10:30:07+02:00 status=FAILED "
+ "mbsync=1 notmuch=0 =====\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Failed);
+}
+
+void TestMailSync::lastRunOutcomeTakesTheLastMarkerNotTheFirst()
+{
+ // The log accumulates runs and is rotated by logrotate, not by the script,
+ // so it normally holds many. Reading the first marker would report an
+ // outcome from hours ago, and in the direction that matters: an old OK
+ // would clear the count for a run that has just failed.
+ const QString path = m_dir.filePath(QStringLiteral("many.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("===== RUN END: 2026-08-09T10:00:03+02:00 status=OK =====\n"
+ "===== RUN END: 2026-08-09T10:10:03+02:00 status=OK =====\n"
+ "===== RUN END: 2026-08-09T10:20:07+02:00 status=FAILED "
+ "mbsync=1 notmuch=0 =====\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Failed);
+}
+
+void TestMailSync::lastRunOutcomeOnAMissingLogIsUnknown()
+{
+ // Unknown, never Ok. The caller clears the user's pending count on Ok, so
+ // an absent log must not be able to assert that edits reached the store.
+ QCOMPARE(MailSync::lastRunOutcome(m_dir.filePath(QStringLiteral("nope.log"))),
+ SyncOutcome::Unknown);
+ QCOMPARE(MailSync::lastRunOutcome(QString()), SyncOutcome::Unknown);
+}
+
+void TestMailSync::lastRunOutcomeOnALogWithNoMarkerIsUnknown()
+{
+ const QString path = m_dir.filePath(QStringLiteral("nomarker.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("10:20:01 Channel one\n10:20:02 Channel two\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Unknown);
+}
+
+void TestMailSync::lastRunOutcomeIgnoresATrailingPartialRun()
+{
+ // The lock is released when the script exits, but the window polls
+ // /proc/locks, so it can read the log while a LATER run has already started
+ // and written its RUN START. Only END lines carry an outcome.
+ const QString path = m_dir.filePath(QStringLiteral("partial.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("===== RUN END: 2026-08-09T10:20:03+02:00 status=OK =====\n"
+ "===== RUN START: 2026-08-09T10:30:00+02:00 =====\n"
+ "10:30:01 Channel one\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Ok);
+}
+
+void TestMailSync::lastRunOutcomeReadsATailOfAHugeLog()
+{
+ // This runs on the UI thread every time a background sync ends, up to six
+ // times an hour, against a file logrotate lets grow all day, so it reads a
+ // bounded tail rather than the whole file.
+ //
+ // Asserted by CONTENT, not by timing. A first version of this test timed
+ // the call and required it under 100 ms; it passed with the seek deleted,
+ // because reading 10 MB is quick enough either way. The probe measured
+ // nothing. A marker reachable only from the head of the file cannot be
+ // found by a tail read and cannot be missed by a whole-file read, so the
+ // two implementations give different answers here.
+ const QString path = m_dir.filePath(QStringLiteral("huge.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write("===== RUN END: 2026-08-09T09:00:03+02:00 status=OK =====\n");
+ const QByteArray filler(200, 'x');
+ for (int i = 0; i < 50000; ++i) {
+ file.write(filler);
+ file.write("\n");
+ }
+ file.close();
+ QVERIFY2(QFileInfo(path).size() > 4L * 1024 * 1024,
+ "the fixture must be big enough to matter");
+
+ // Unknown, because the only marker is megabytes above the tail. Reading the
+ // whole file would return Ok and fail this.
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Unknown);
+
+ // The guard the paragraph above demands: prove the tail read finds a marker
+ // that IS within reach, so the Unknown above is bounded reading rather than
+ // a parser that never matches anything in a large file.
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text));
+ file.write("===== RUN END: 2026-08-09T10:20:03+02:00 status=FAILED "
+ "mbsync=1 notmuch=0 =====\n");
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Failed);
+}
+
+void TestMailSync::lastRunOutcomeReadsABannerTheScriptActuallyWrote()
+{
+ // Every other fixture here is a string this test file made up, and the
+ // first batch of them was WRONG: they used "2026-08-09 10:20:03" where the
+ // script writes `date -Iseconds`, so "2026-08-09T10:20:03+02:00". The
+ // parser happened to survive it, because it keys on the "===== RUN END:"
+ // prefix and the status= token rather than on the timestamp, but nothing
+ // here proved the two formats agreed. A fixture invented to match the code
+ // tests the code against itself.
+ //
+ // So build the banner the way assets/mailsync.sh builds it, with the same
+ // command, and parse that. If the script's format changes, or the parser
+ // starts depending on the timestamp shape, this fails.
+ QProcess date;
+ date.start(QStringLiteral("date"), { QStringLiteral("-Iseconds") });
+ QVERIFY(date.waitForFinished(5000));
+ QCOMPARE(date.exitCode(), 0);
+ const QString stamp =
+ QString::fromUtf8(date.readAllStandardOutput()).trimmed();
+ QVERIFY(!stamp.isEmpty());
+
+ const QString path = m_dir.filePath(QStringLiteral("real.log"));
+ QFile file(path);
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Text));
+ file.write(QStringLiteral("===== RUN END: %1 status=OK =====\n")
+ .arg(stamp).toUtf8());
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Ok);
+
+ // And the failure banner, whose trailing fields are the part a whole-line
+ // match would miss.
+ QVERIFY(file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text));
+ file.write(QStringLiteral("===== RUN END: %1 status=FAILED mbsync=1 "
+ "notmuch=0 =====\n").arg(stamp).toUtf8());
+ file.close();
+
+ QCOMPARE(MailSync::lastRunOutcome(path), SyncOutcome::Failed);
+}
+
QTEST_MAIN(TestMailSync)
#include "test_mailsync.moc"