summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_mainwindow.cpp97
-rw-r--r--tests/test_notmuchworker.cpp44
2 files changed, 141 insertions, 0 deletions
diff --git a/tests/test_mainwindow.cpp b/tests/test_mainwindow.cpp
index 2625bf5..ef6f99d 100644
--- a/tests/test_mainwindow.cpp
+++ b/tests/test_mainwindow.cpp
@@ -78,6 +78,8 @@ private slots:
void theStatusBarReportsAMultiRowSelection();
void clearSelectionBlanksThePaneAndDeselects();
void clearPaneLeavesTheSelectionAlone();
+ void maildirOverviewShowsUnknownRatherThanZero();
+ void maildirOverviewIgnoresAStaleReply();
void theThreadListOffersAContextMenu();
void aSecondRowBlanksThePaneNotOnlyAThird();
void aLocalSyncIsNotReportedAsABackgroundOne();
@@ -1179,6 +1181,101 @@ void TestMainWindow::clearPaneLeavesTheSelectionAlone()
QCOMPARE(view->selectionModel()->selectedRows().size(), 1);
}
+void TestMainWindow::maildirOverviewShowsUnknownRatherThanZero()
+{
+ // A field notmuch could not answer must not render as 0. "0 messages" says
+ // the Maildir is empty, which is a claim; the truth is that the count
+ // failed, and telling someone their mail is gone is the worst available
+ // way to report an unreadable index.
+ const Config config;
+ MainWindow window(config);
+
+ auto *action = window.findChild<QAction *>(QStringLiteral("maildirOverview"));
+ QVERIFY2(action, "no maildirOverview action");
+ action->trigger();
+
+ auto *counts = window.findChild<QLabel *>(QStringLiteral("maildirCounts"));
+ QVERIFY2(counts, "the overview dialog has no counts label");
+
+ // The worker never answers in this fixture, so drive the slot directly
+ // with the all-unknown stats a failed open produces.
+ QMetaObject::invokeMethod(
+ &window, "onDatabaseStatsReady", Qt::DirectConnection,
+ Q_ARG(DatabaseStats, DatabaseStats{}),
+ Q_ARG(quint64, window.statsGenerationForTesting()));
+
+ QVERIFY2(counts->text().contains(QStringLiteral("unknown")),
+ qPrintable(QStringLiteral("counts label says '%1'")
+ .arg(counts->text())));
+ QVERIFY2(!counts->text().contains(QStringLiteral(">0<")),
+ qPrintable(QStringLiteral("an unanswered count rendered as zero: "
+ "'%1'").arg(counts->text())));
+
+ // WA_DeleteOnClose, so closing is what frees it. Left open, each test
+ // leaks a window for the rest of the run.
+ counts->window()->close();
+}
+
+void TestMainWindow::maildirOverviewIgnoresAStaleReply()
+{
+ // Counting every message is slow enough that closing and reopening the
+ // dialog while one runs is realistic. The older answer must not fill in the
+ // newer dialog, or the numbers silently predate whatever prompted the
+ // reopen.
+ const Config config;
+ MainWindow window(config);
+
+ auto *action = window.findChild<QAction *>(QStringLiteral("maildirOverview"));
+ QVERIFY(action);
+ action->trigger();
+
+ const quint64 stale = window.statsGenerationForTesting();
+
+ // Reopening bumps the generation, which is what makes the first reply old.
+ action->trigger();
+ QVERIFY2(window.statsGenerationForTesting() != stale,
+ "reopening the dialog did not bump the generation, so a reply for "
+ "the previous one cannot be told apart");
+
+ auto *counts = window.findChild<QLabel *>(QStringLiteral("maildirCounts"));
+ QVERIFY(counts);
+ const QString before = counts->text();
+
+ DatabaseStats old;
+ old.messages = 4321;
+ old.threads = 999;
+ old.tags = 42;
+ QMetaObject::invokeMethod(&window, "onDatabaseStatsReady",
+ Qt::DirectConnection,
+ Q_ARG(DatabaseStats, old),
+ Q_ARG(quint64, stale));
+
+ QCOMPARE(counts->text(), before);
+ QVERIFY2(!counts->text().contains(QStringLiteral("4321")),
+ "a reply for the previous dialog filled in the current one");
+
+ QPointer<QLabel> watch(counts);
+ counts->window()->close();
+
+ // WA_DeleteOnClose deletes through deleteLater, so the label outlives
+ // close() until the event loop runs. Drain it, or the "reply after the
+ // dialog is gone" case below is not actually being tested.
+ QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
+ QVERIFY2(watch.isNull(),
+ "the dialog was not destroyed, so the case below is not the one "
+ "this test means to exercise");
+
+ // The QPointer's reason for being: counting a large database takes long
+ // enough that closing the dialog first is ordinary, and the reply then
+ // arrives for a label that has been deleted. A raw pointer would dangle
+ // here, so this must not crash.
+ QMetaObject::invokeMethod(&window, "onDatabaseStatsReady",
+ Qt::DirectConnection,
+ Q_ARG(DatabaseStats, old),
+ Q_ARG(quint64,
+ window.statsGenerationForTesting()));
+}
+
void TestMainWindow::theThreadListOffersAContextMenu()
{
// Right-click is the other half of discoverability: until now every tag
diff --git a/tests/test_notmuchworker.cpp b/tests/test_notmuchworker.cpp
index be62ad3..b419915 100644
--- a/tests/test_notmuchworker.cpp
+++ b/tests/test_notmuchworker.cpp
@@ -60,6 +60,8 @@ private slots:
void requestCountsAnswersOneCountPerQuery();
void requestCountsKeepsPositionOnAnInvalidQuery();
+ void requestDatabaseStatsCountsMessagesNotThreads();
+ void requestDatabaseStatsOnUnreadableConfigEmitsError();
private:
/// Tags of one message, read back through a fresh worker query.
@@ -523,5 +525,47 @@ void TestNotmuchWorker::requestCountsKeepsPositionOnAnInvalidQuery()
QCOMPARE(counts.at(2), 3);
}
+void TestNotmuchWorker::requestDatabaseStatsCountsMessagesNotThreads()
+{
+ NotmuchWorker worker(m_fixture.configPath());
+ QSignalSpy spy(&worker, &NotmuchWorker::databaseStatsReady);
+
+ worker.requestDatabaseStats(11);
+
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(spy.at(0).at(1).value<quint64>(), quint64(11));
+ const auto stats = spy.at(0).at(0).value<DatabaseStats>();
+
+ // The fixture holds four messages in three threads: thread A is a message
+ // and its reply. **That difference is the whole point of this call.**
+ // requestCounts() counts threads, to match the row count of a query; this
+ // one counts messages, which is what a user means by "how much mail". A
+ // reimplementation that reused the thread count would report 3 here and be
+ // confidently wrong under the label "messages".
+ QCOMPARE(stats.messages, 4);
+ QCOMPARE(stats.threads, 3);
+ QVERIFY2(stats.messages != stats.threads,
+ "messages and threads are equal, so this fixture cannot prove the "
+ "two counts are distinct: add a reply to it");
+
+ // Every tag the fixture creates, plus notmuch's own.
+ QVERIFY(stats.tags > 0);
+}
+
+void TestNotmuchWorker::requestDatabaseStatsOnUnreadableConfigEmitsError()
+{
+ // Fails closed like every other entry point. The dialog then shows its
+ // fields as unknown rather than as zero, since "no mail at all" is the
+ // wrong thing to tell someone whose index failed to open.
+ NotmuchWorker worker(QStringLiteral("/nonexistent/qtmaildir-test/config"));
+ QSignalSpy ready(&worker, &NotmuchWorker::databaseStatsReady);
+ QSignalSpy errors(&worker, &NotmuchWorker::errorOccurred);
+
+ worker.requestDatabaseStats(1);
+
+ QCOMPARE(errors.size(), 1);
+ QVERIFY(ready.isEmpty());
+}
+
QTEST_MAIN(TestNotmuchWorker)
#include "test_notmuchworker.moc"