[geary/wip/794174-conversation-monitor-max-cpu: 3/3] Add unit tests for ConversationMonitor, fix a few issues.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/794174-conversation-monitor-max-cpu: 3/3] Add unit tests for ConversationMonitor, fix a few issues.
- Date: Wed, 4 Apr 2018 06:02:57 +0000 (UTC)
commit b8f228ca38fb93071be1c03470dd069bf4e8a38b
Author: Michael James Gratton <mike vee net>
Date: Wed Apr 4 15:58:44 2018 +1000
Add unit tests for ConversationMonitor, fix a few issues.
* src/engine/app/app-conversation-monitor.vala (ConversationMonitor):
Don't check the folder blacklist for external folder signals when the
signal is received, do it when the operation is executed so it's fresh.
* src/engine/app/conversation-monitor/app-fill-window-operation.vala
(FillWindowOperation): Only re-fill the conversation if we get a full
load, anything else means we hit the end of the folder.
* test/engine/app/app-conversation-monitor-test.vala: New unit tests
for ConversationMonitor.
* test/engine/api/geary-account-mock.vala,
test/engine/api/geary-folder-mock.vala: Mix in MockObject mock up all
method calls.
* test/engine/api/geary-email-identifier-mock.vala: Fix the comparator
method when the other instance is null.
src/engine/app/app-conversation-monitor.vala | 12 +-
.../app-fill-window-operation.vala | 2 +-
test/CMakeLists.txt | 1 +
test/engine/api/geary-account-mock.vala | 158 +++++--
test/engine/api/geary-email-identifier-mock.vala | 2 +-
test/engine/api/geary-folder-mock.vala | 32 ++-
test/engine/app/app-conversation-monitor-test.vala | 456 ++++++++++++++++++++
test/meson.build | 1 +
test/test-engine.vala | 2 +
9 files changed, 609 insertions(+), 57 deletions(-)
---
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index 4234825..b7541f5 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -802,8 +802,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
private void on_account_email_appended(Folder folder,
Gee.Collection<EmailIdentifier> added) {
- if (folder != this.base_folder &&
- !get_search_folder_blacklist().contains(folder.path)) {
+ if (folder != this.base_folder) {
this.queue.add(new ExternalAppendOperation(this, folder, added));
}
}
@@ -813,8 +812,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
// ExternalAppendOperation will check to determine if the
// email is relevant for some existing conversation before
// adding it, which is what we want here.
- if (folder != this.base_folder &&
- !get_search_folder_blacklist().contains(folder.path)) {
+ if (folder != this.base_folder) {
this.queue.add(new ExternalAppendOperation(this, folder, inserted));
}
}
@@ -824,16 +822,14 @@ public class Geary.App.ConversationMonitor : BaseObject {
// ExternalAppendOperation will check to determine if the
// email is relevant for some existing conversation before
// adding it, which is what we want here.
- if (folder != this.base_folder &&
- !get_search_folder_blacklist().contains(folder.path)) {
+ if (folder != this.base_folder) {
this.queue.add(new ExternalAppendOperation(this, folder, inserted));
}
}
private void on_account_email_removed(Folder folder,
Gee.Collection<EmailIdentifier> removed) {
- if (folder != this.base_folder &&
- !get_search_folder_blacklist().contains(folder.path)) {
+ if (folder != this.base_folder) {
this.queue.add(new RemoveOperation(this, folder, removed));
}
}
diff --git a/src/engine/app/conversation-monitor/app-fill-window-operation.vala
b/src/engine/app/conversation-monitor/app-fill-window-operation.vala
index 456680c..5348984 100644
--- a/src/engine/app/conversation-monitor/app-fill-window-operation.vala
+++ b/src/engine/app/conversation-monitor/app-fill-window-operation.vala
@@ -49,7 +49,7 @@ private class Geary.App.FillWindowOperation : ConversationOperation {
// Check to see if we need any more, but only if we actually
// loaded some, so we don't keep loop loading when we have
// already loaded all in the folder.
- if (loaded > 0) {
+ if (loaded == num_to_load) {
this.monitor.check_window_count();
}
}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 2d16ff0..f1ad286 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -24,6 +24,7 @@ set(TEST_ENGINE_SRC
engine/api/geary-attachment-test.vala
engine/api/geary-engine-test.vala
engine/app/app-conversation-test.vala
+ engine/app/app-conversation-monitor-test.vala
engine/app/app-conversation-set-test.vala
engine/imap/command/imap-create-command-test.vala
engine/imap/response/imap-namespace-response-test.vala
diff --git a/test/engine/api/geary-account-mock.vala b/test/engine/api/geary-account-mock.vala
index 2ddd799..ebc2552 100644
--- a/test/engine/api/geary-account-mock.vala
+++ b/test/engine/api/geary-account-mock.vala
@@ -5,7 +5,7 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.MockAccount : Account {
+public class Geary.MockAccount : Account, MockObject {
public class MockSearchQuery : SearchQuery {
@@ -31,100 +31,176 @@ public class Geary.MockAccount : Account {
}
+ protected Gee.Queue<ExpectedCall> expected {
+ get; set; default = new Gee.LinkedList<ExpectedCall>();
+ }
+
+
public MockAccount(string name, AccountInformation information) {
base(name, information);
}
public override async void open_async(Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ void_call("open_async", { cancellable });
}
public override async void close_async(Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ void_call("close_async", { cancellable });
}
public override bool is_open() {
- return false;
+ try {
+ return boolean_call("is_open", {}, false);
+ } catch (Error err) {
+ return false;
+ }
}
public override async void rebuild_async(Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ void_call("rebuild_async", { cancellable });
}
public override async void start_outgoing_client()
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ void_call("start_outgoing_client", {});
}
public override async void start_incoming_client()
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ void_call("start_incoming_client", {});
}
- public override Gee.Collection<Geary.Folder> list_matching_folders(Geary.FolderPath? parent)
+ public override Gee.Collection<Folder> list_matching_folders(FolderPath? parent)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return object_call<Gee.Collection<Folder>>(
+ "get_containing_folders_async", {parent}, Gee.List.empty<Folder>()
+ );
}
- public override Gee.Collection<Geary.Folder> list_folders() throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override Gee.Collection<Folder> list_folders() throws Error {
+ return object_call<Gee.Collection<Folder>>(
+ "list_folders", {}, Gee.List.empty<Folder>()
+ );
}
public override Geary.ContactStore get_contact_store() {
return new MockContactStore();
}
- public override async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null)
+ public override async bool folder_exists_async(FolderPath path,
+ Cancellable? cancellable = null)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return boolean_call("folder_exists_async", {path, cancellable}, false);
}
- public override async Geary.Folder fetch_folder_async(Geary.FolderPath path,
- Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override async Folder fetch_folder_async(FolderPath path,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_or_throw_call<Folder>(
+ "fetch_folder_async",
+ {path, cancellable},
+ new EngineError.NOT_FOUND("Mock call")
+ );
}
- public override async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special,
- Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
- }
-
- public override async void send_email_async(Geary.ComposedEmail composed, Cancellable? cancellable =
null)
+ public override Folder? get_special_folder(SpecialFolderType special)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return object_call<Folder?>(
+ "get_special_folder", {box_arg(special)}, null
+ );
}
- public override async Gee.MultiMap<Geary.Email, Geary.FolderPath?>? local_search_message_id_async(
- Geary.RFC822.MessageID message_id, Geary.Email.Field requested_fields, bool partial_ok,
- Gee.Collection<Geary.FolderPath?>? folder_blacklist, Geary.EmailFlags? flag_blacklist,
- Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override async Folder get_required_special_folder_async(SpecialFolderType special,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_or_throw_call<Folder>(
+ "get_required_special_folder_async",
+ {box_arg(special), cancellable},
+ new EngineError.NOT_FOUND("Mock call")
+ );
}
- public override async Geary.Email local_fetch_email_async(Geary.EmailIdentifier email_id,
- Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override async void send_email_async(ComposedEmail composed,
+ Cancellable? cancellable = null)
+ throws Error {
+ void_call("send_email_async", {composed, cancellable});
}
- public override Geary.SearchQuery open_search(string query, Geary.SearchQuery.Strategy strategy) {
- return new MockSearchQuery();
+ public override async Gee.MultiMap<Email,FolderPath?>?
+ local_search_message_id_async(RFC822.MessageID message_id,
+ Email.Field requested_fields,
+ bool partial_ok,
+ Gee.Collection<FolderPath?>? folder_blacklist,
+ EmailFlags? flag_blacklist,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_call<Gee.MultiMap<Email,FolderPath?>?>(
+ "local_search_message_id_async",
+ {
+ message_id,
+ box_arg(requested_fields),
+ box_arg(partial_ok),
+ folder_blacklist,
+ flag_blacklist,
+ cancellable
+ },
+ null
+ );
+ }
+
+ public override async Email local_fetch_email_async(EmailIdentifier email_id,
+ Email.Field required_fields,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_or_throw_call<Email>(
+ "local_fetch_email_async",
+ {email_id, box_arg(required_fields), cancellable},
+ new EngineError.NOT_FOUND("Mock call")
+ );
}
- public override async Gee.Collection<Geary.EmailIdentifier>? local_search_async(Geary.SearchQuery query,
- int limit = 100, int offset = 0, Gee.Collection<Geary.FolderPath?>? folder_blacklist = null,
- Gee.Collection<Geary.EmailIdentifier>? search_ids = null, Cancellable? cancellable = null) throws
Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override SearchQuery open_search(string query, SearchQuery.Strategy strategy) {
+ return new MockSearchQuery();
}
- public override async Gee.Set<string>? get_search_matches_async(Geary.SearchQuery query,
- Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ public override async Gee.Collection<EmailIdentifier>?
+ local_search_async(SearchQuery query,
+ int limit = 100,
+ int offset = 0,
+ Gee.Collection<FolderPath?>? folder_blacklist = null,
+ Gee.Collection<EmailIdentifier>? search_ids = null,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_call<Gee.Collection<EmailIdentifier>?>(
+ "local_search_async",
+ {
+ query,
+ box_arg(limit),
+ box_arg(offset),
+ folder_blacklist,
+ search_ids,
+ cancellable
+ },
+ null
+ );
+ }
+
+ public override async Gee.Set<string>?
+ get_search_matches_async(SearchQuery query,
+ Gee.Collection<EmailIdentifier> ids,
+ Cancellable? cancellable = null)
+ throws Error {
+ return object_call<Gee.Set<string>?>(
+ "get_search_matches_async", {query, ids, cancellable}, null
+ );
}
public override async Gee.MultiMap<EmailIdentifier, FolderPath>?
get_containing_folders_async(Gee.Collection<EmailIdentifier> ids,
Cancellable? cancellable) throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return object_call<Gee.MultiMap<EmailIdentifier, FolderPath>?>(
+ "get_containing_folders_async", {ids, cancellable}, null
+ );
}
}
diff --git a/test/engine/api/geary-email-identifier-mock.vala
b/test/engine/api/geary-email-identifier-mock.vala
index adda6ed..83367ea 100644
--- a/test/engine/api/geary-email-identifier-mock.vala
+++ b/test/engine/api/geary-email-identifier-mock.vala
@@ -18,7 +18,7 @@ public class Geary.MockEmailIdentifer : EmailIdentifier {
public override int natural_sort_comparator(Geary.EmailIdentifier other) {
MockEmailIdentifer? other_mock = other as MockEmailIdentifer;
- return (other_mock == null) ? -1 : other_mock.id - this.id;
+ return (other_mock == null) ? 1 : this.id - other_mock.id;
}
}
diff --git a/test/engine/api/geary-folder-mock.vala b/test/engine/api/geary-folder-mock.vala
index 55b8e5b..fee33d4 100644
--- a/test/engine/api/geary-folder-mock.vala
+++ b/test/engine/api/geary-folder-mock.vala
@@ -5,7 +5,8 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
-public class Geary.MockFolder : Folder {
+public class Geary.MockFolder : Folder, MockObject {
+
public override Account account {
get { return this._account; }
@@ -27,6 +28,11 @@ public class Geary.MockFolder : Folder {
get { return this._opening_monitor; }
}
+ protected Gee.Queue<ExpectedCall> expected {
+ get; set; default = new Gee.LinkedList<ExpectedCall>();
+ }
+
+
private Account _account;
private FolderProperties _properties;
private FolderPath _path;
@@ -52,8 +58,12 @@ public class Geary.MockFolder : Folder {
public override async bool open_async(Folder.OpenFlags open_flags,
Cancellable? cancellable = null)
- throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ throws Error {
+ return boolean_call(
+ "open_async",
+ { int_arg(open_flags), cancellable },
+ false
+ );
}
public override async void wait_for_remote_async(Cancellable? cancellable = null)
@@ -63,7 +73,9 @@ public class Geary.MockFolder : Folder {
public override async bool close_async(Cancellable? cancellable = null)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return boolean_call(
+ "close_async", { cancellable }, false
+ );
}
public override async void wait_for_close_async(Cancellable? cancellable = null)
@@ -78,7 +90,11 @@ public class Geary.MockFolder : Folder {
Folder.ListFlags flags,
Cancellable? cancellable = null)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return object_call<Gee.List<Email>?>(
+ "list_email_by_id_async",
+ {initial_id, int_arg(count), box_arg(required_fields), box_arg(flags), cancellable},
+ null
+ );
}
public override async Gee.List<Geary.Email>?
@@ -87,7 +103,11 @@ public class Geary.MockFolder : Folder {
Folder.ListFlags flags,
Cancellable? cancellable = null)
throws Error {
- throw new EngineError.UNSUPPORTED("Mock method");
+ return object_call<Gee.List<Email>?>(
+ "list_email_by_sparse_id_async",
+ {ids, box_arg(required_fields), box_arg(flags), cancellable},
+ null
+ );
}
public override async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>?
diff --git a/test/engine/app/app-conversation-monitor-test.vala
b/test/engine/app/app-conversation-monitor-test.vala
new file mode 100644
index 0000000..52e4f9c
--- /dev/null
+++ b/test/engine/app/app-conversation-monitor-test.vala
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2018 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+
+class Geary.App.ConversationMonitorTest : TestCase {
+
+
+ AccountInformation? account_info = null;
+ MockAccount? account = null;
+ MockFolder? base_folder = null;
+ MockFolder? other_folder = null;
+
+
+ public ConversationMonitorTest() {
+ base("Geary.App.ConversationMonitorTest");
+ add_test("start_stop_monitoring", start_stop_monitoring);
+ add_test("open_error", open_error);
+ add_test("load_single_message", load_single_message);
+ add_test("load_multiple_messages", load_multiple_messages);
+ add_test("load_related_message", load_related_message);
+ add_test("base_folder_message_appended", base_folder_message_appended);
+ add_test("base_folder_message_removed", base_folder_message_removed);
+ add_test("external_folder_message_appended", external_folder_message_appended);
+ }
+
+ public override void set_up() {
+ this.account_info = new AccountInformation(
+ "account_01",
+ File.new_for_path("/tmp"),
+ File.new_for_path("/tmp")
+ );
+ this.account = new MockAccount("test", this.account_info);
+ this.base_folder = new MockFolder(
+ this.account,
+ null,
+ new MockFolderRoot("base"),
+ SpecialFolderType.NONE,
+ null
+ );
+ this.other_folder = new MockFolder(
+ this.account,
+ null,
+ new MockFolderRoot("other"),
+ SpecialFolderType.NONE,
+ null
+ );
+ }
+
+ public void start_stop_monitoring() throws Error {
+ ConversationMonitor monitor = new ConversationMonitor(
+ this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10
+ );
+ Cancellable test_cancellable = new Cancellable();
+
+ this.base_folder.expect_call(
+ "open_async",
+ { MockObject.int_arg(Folder.OpenFlags.NONE), test_cancellable }
+ );
+ this.base_folder.expect_call("list_email_by_id_async");
+ this.base_folder.expect_call("close_async");
+
+ monitor.start_monitoring_async.begin(
+ test_cancellable, (obj, res) => { async_complete(res); }
+ );
+ monitor.start_monitoring_async.end(async_result());
+
+ monitor.stop_monitoring_async.begin(
+ test_cancellable, (obj, res) => { async_complete(res); }
+ );
+ monitor.stop_monitoring_async.end(async_result());
+
+ this.base_folder.assert_expectations();
+ }
+
+ public void open_error() throws Error {
+ ConversationMonitor monitor = new ConversationMonitor(
+ this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10
+ );
+
+ ExpectedCall open = this.base_folder
+ .expect_call("open_async")
+ .throws(new EngineError.SERVER_UNAVAILABLE("Mock error"));
+
+ monitor.start_monitoring_async.begin(
+ null, (obj, res) => { async_complete(res); }
+ );
+ try {
+ monitor.start_monitoring_async.end(async_result());
+ assert_not_reached();
+ } catch (Error err) {
+ assert_error(open.throw_error, err);
+ }
+
+ this.base_folder.assert_expectations();
+ }
+
+ public void load_single_message() throws Error {
+ Email e1 = setup_email(1);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.base_folder.path);
+
+ ConversationMonitor monitor = setup_monitor({e1}, paths);
+
+ assert_int(1, monitor.size, "Conversation count");
+ assert_non_null(monitor.window_lowest, "Lowest window id");
+ assert_equal(e1.id, monitor.window_lowest, "Lowest window id");
+
+ Conversation c1 = Geary.Collection.get_first(monitor.read_only_view);
+ assert_equal(e1, c1.get_email_by_id(e1.id), "Email not present in conversation");
+ }
+
+ public void load_multiple_messages() throws Error {
+ Email e1 = setup_email(1, null);
+ Email e2 = setup_email(2, null);
+ Email e3 = setup_email(3, null);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.base_folder.path);
+ paths.set(e2.id, this.base_folder.path);
+ paths.set(e3.id, this.base_folder.path);
+
+ ConversationMonitor monitor = setup_monitor({e3, e2, e1}, paths);
+
+ assert_int(3, monitor.size, "Conversation count");
+ assert_non_null(monitor.window_lowest, "Lowest window id");
+ assert_equal(e1.id, monitor.window_lowest, "Lowest window id");
+ }
+
+ public void load_related_message() throws Error {
+ Email e1 = setup_email(1);
+ Email e2 = setup_email(2, e1);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.other_folder.path);
+ paths.set(e2.id, this.base_folder.path);
+
+ Gee.MultiMap<Email,FolderPath> related_paths =
+ new Gee.HashMultiMap<Email,FolderPath>();
+ related_paths.set(e1, this.other_folder.path);
+ related_paths.set(e2, this.base_folder.path);
+
+ ConversationMonitor monitor = setup_monitor({e2}, paths, {related_paths});
+
+ assert_int(1, monitor.size, "Conversation count");
+ assert_non_null(monitor.window_lowest, "Lowest window id");
+ assert_equal(e2.id, monitor.window_lowest, "Lowest window id");
+
+ Conversation c1 = Geary.Collection.get_first(monitor.read_only_view);
+ assert_equal(e1, c1.get_email_by_id(e1.id), "Related email not present in conversation");
+ assert_equal(e2, c1.get_email_by_id(e2.id), "In folder not present in conversation");
+ }
+
+ public void base_folder_message_appended() throws Error {
+ Email e1 = setup_email(1);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.base_folder.path);
+
+ ConversationMonitor monitor = setup_monitor();
+ assert_int(0, monitor.size, "Initial conversation count");
+
+ this.base_folder.expect_call("list_email_by_sparse_id_async")
+ .returns_object(new Gee.ArrayList<Email>.wrap({e1}));
+
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("local_search_message_id_async");
+
+ this.account.expect_call("get_containing_folders_async")
+ .returns_object(paths);
+
+ this.base_folder.email_appended(new Gee.ArrayList<EmailIdentifier>.wrap({e1.id}));
+
+ wait_for_signal(monitor, "conversations-added");
+ this.base_folder.assert_expectations();
+ this.account.assert_expectations();
+
+ assert_int(1, monitor.size, "Conversation count");
+ }
+
+ public void base_folder_message_removed() throws Error {
+ Email e1 = setup_email(1);
+ Email e2 = setup_email(2, e1);
+ Email e3 = setup_email(3);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.other_folder.path);
+ paths.set(e2.id, this.base_folder.path);
+ paths.set(e3.id, this.base_folder.path);
+
+ Gee.MultiMap<Email,FolderPath> e2_related_paths =
+ new Gee.HashMultiMap<Email,FolderPath>();
+ e2_related_paths.set(e1, this.other_folder.path);
+ e2_related_paths.set(e2, this.base_folder.path);
+
+ ConversationMonitor monitor = setup_monitor(
+ {e3, e2}, paths, {null, e2_related_paths}
+ );
+ assert_int(2, monitor.size, "Initial conversation count");
+ print("monitor.window_lowest: %s", monitor.window_lowest.to_string());
+ assert_equal(e2.id, monitor.window_lowest, "Lowest window id");
+
+ // Removing a message will trigger another async load
+ this.base_folder.expect_call("list_email_by_id_async");
+ this.account.expect_call("get_containing_folders_async");
+ this.base_folder.expect_call("list_email_by_id_async");
+
+ this.base_folder.email_removed(new Gee.ArrayList<EmailIdentifier>.wrap({e2.id}));
+ wait_for_signal(monitor, "conversations-removed");
+ assert_int(1, monitor.size, "Conversation count");
+ assert_equal(e3.id, monitor.window_lowest, "Lowest window id");
+
+ this.base_folder.email_removed(new Gee.ArrayList<EmailIdentifier>.wrap({e3.id}));
+ wait_for_signal(monitor, "conversations-removed");
+ assert_int(0, monitor.size, "Conversation count");
+ assert_null(monitor.window_lowest, "Lowest window id");
+
+ // Close the monitor to cancel the final load so it does not
+ // error out during later tests
+ this.base_folder.expect_call("close_async");
+ monitor.stop_monitoring_async.begin(
+ null, (obj, res) => { async_complete(res); }
+ );
+ monitor.stop_monitoring_async.end(async_result());
+ }
+
+ public void external_folder_message_appended() throws Error {
+ Email e1 = setup_email(1);
+ Email e2 = setup_email(2, e1);
+ Email e3 = setup_email(3, e1);
+
+ Gee.MultiMap<EmailIdentifier,FolderPath> paths =
+ new Gee.HashMultiMap<EmailIdentifier,FolderPath>();
+ paths.set(e1.id, this.base_folder.path);
+ paths.set(e2.id, this.base_folder.path);
+ paths.set(e3.id, this.other_folder.path);
+
+ Gee.MultiMap<Email,FolderPath> related_paths =
+ new Gee.HashMultiMap<Email,FolderPath>();
+ related_paths.set(e1, this.base_folder.path);
+ related_paths.set(e3, this.other_folder.path);
+
+ ConversationMonitor monitor = setup_monitor({e1}, paths);
+ assert_int(1, monitor.size, "Initial conversation count");
+
+ this.other_folder.expect_call("open_async");
+ this.other_folder.expect_call("list_email_by_sparse_id_async")
+ .returns_object(new Gee.ArrayList<Email>.wrap({e3}));
+ this.other_folder.expect_call("list_email_by_sparse_id_async")
+ .returns_object(new Gee.ArrayList<Email>.wrap({e3}));
+ this.other_folder.expect_call("close_async");
+
+ // ExternalAppendOperation's blacklist check
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+
+ /////////////////////////////////////////////////////////
+ // First call to expand_conversations_async for e3's refs
+
+ // LocalSearchOperationAppendOperation's blacklist check
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+
+ // Search for e1's ref
+ this.account.expect_call("local_search_message_id_async")
+ .returns_object(related_paths);
+
+ // Search for e2's ref
+ this.account.expect_call("local_search_message_id_async");
+
+ //////////////////////////////////////////////////////////
+ // Second call to expand_conversations_async for e1's refs
+
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("local_search_message_id_async");
+
+ // Finally, the call to process_email_complete_async
+
+ this.account.expect_call("get_containing_folders_async")
+ .returns_object(paths);
+
+ // Should not be added, since it's actually in the base folder
+ this.account.email_appended(
+ this.base_folder,
+ new Gee.ArrayList<EmailIdentifier>.wrap({e2.id})
+ );
+
+ // Should be added, since it's an external message
+ this.account.email_appended(
+ this.other_folder,
+ new Gee.ArrayList<EmailIdentifier>.wrap({e3.id})
+ );
+
+ wait_for_signal(monitor, "conversations-added");
+ this.base_folder.assert_expectations();
+ this.other_folder.assert_expectations();
+ this.account.assert_expectations();
+
+ assert_int(1, monitor.size, "Conversation count");
+
+ Conversation c1 = Geary.Collection.get_first(monitor.read_only_view);
+ assert_int(2, c1.get_count(), "Conversation message count");
+ assert_equal(e3, c1.get_email_by_id(e3.id),
+ "Appended email not present in conversation");
+ }
+
+ private Email setup_email(int id, Email? references = null) {
+ Email email = new Email(new MockEmailIdentifer(id));
+ DateTime now = new DateTime.now_local();
+ Geary.RFC822.MessageID mid = new Geary.RFC822.MessageID(
+ "test%d@localhost".printf(id)
+ );
+
+ Geary.RFC822.MessageIDList refs_list = null;
+ if (references != null) {
+ refs_list = new Geary.RFC822.MessageIDList.single(
+ references.message_id
+ );
+ }
+ email.set_send_date(new Geary.RFC822.Date.from_date_time(now));
+ email.set_email_properties(new MockEmailProperties(now));
+ email.set_full_references(mid, null, refs_list);
+ return email;
+ }
+
+ private ConversationMonitor
+ setup_monitor(Email[] base_folder_email = {},
+ Gee.MultiMap<EmailIdentifier,FolderPath>? paths = null,
+ Gee.MultiMap<Email,FolderPath>[] related_paths = {})
+ throws Error {
+ ConversationMonitor monitor = new ConversationMonitor(
+ this.base_folder, Folder.OpenFlags.NONE, Email.Field.NONE, 10
+ );
+ Cancellable test_cancellable = new Cancellable();
+
+ /*
+ * The process for loading messages looks roughly like this:
+ * - load_by_id_async
+ * - base_folder.list_email_by_id_async
+ * - process_email_async
+ * - gets all related messages from listing
+ * - expand_conversations_async
+ * - get_search_folder_blacklist (i.e. account.get_special_folder × 3)
+ * - foreach related: account.local_search_message_id_async
+ * - process_email_async
+ * - process_email_complete_async
+ * - get_containing_folders_async
+ */
+
+ this.base_folder.expect_call("open_async");
+ ExpectedCall list_call = this.base_folder
+ .expect_call("list_email_by_id_async")
+ .returns_object(new Gee.ArrayList<Email>.wrap(base_folder_email));
+
+ if (base_folder_email.length > 0) {
+ // expand_conversations_async calls
+ // Account:get_special_folder() in
+ // get_search_folder_blacklist, and the default
+ // implementation of that calls get_special_folder.
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+
+ Gee.List<RFC822.MessageID> base_email_ids =
+ new Gee.ArrayList<RFC822.MessageID>();
+ foreach (Email base_email in base_folder_email) {
+ base_email_ids.add(base_email.message_id);
+ }
+
+ int base_i = 0;
+ bool has_related = (
+ base_folder_email.length == related_paths.length
+ );
+ bool found_related = false;
+ Gee.Set<RFC822.MessageID> seen_ids = new Gee.HashSet<RFC822.MessageID>();
+ foreach (Email base_email in base_folder_email) {
+ ExpectedCall call =
+ this.account.expect_call("local_search_message_id_async");
+ seen_ids.add(base_email.message_id);
+ if (has_related && related_paths[base_i] != null) {
+ call.returns_object(related_paths[base_i++]);
+ found_related = true;
+ }
+
+ foreach (RFC822.MessageID ancestor in base_email.get_ancestors()) {
+ if (!seen_ids.contains(ancestor) && !base_email_ids.contains(ancestor)) {
+ this.account.expect_call("local_search_message_id_async");
+ seen_ids.add(ancestor);
+ }
+ }
+ }
+
+ // Second call to expand_conversations_async will be made
+ // if any related were loaded
+ if (found_related) {
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+ this.account.expect_call("get_special_folder");
+
+ seen_ids.clear();
+ foreach (Gee.MultiMap<Email,FolderPath> related in related_paths) {
+ if (related != null) {
+ foreach (Email email in related.get_keys()) {
+ if (!base_email_ids.contains(email.message_id)) {
+ foreach (RFC822.MessageID ancestor in email.get_ancestors()) {
+ if (!seen_ids.contains(ancestor)) {
+ this.account.expect_call("local_search_message_id_async");
+ seen_ids.add(ancestor);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ExpectedCall contains =
+ this.account.expect_call("get_containing_folders_async");
+ if (paths != null) {
+ contains.returns_object(paths);
+ }
+ }
+
+ monitor.start_monitoring_async.begin(
+ test_cancellable, (obj, res) => { async_complete(res); }
+ );
+ monitor.start_monitoring_async.end(async_result());
+
+ if (base_folder_email.length == 0) {
+ wait_for_call(list_call);
+ } else {
+ wait_for_signal(monitor, "conversations-added");
+ }
+
+ this.base_folder.assert_expectations();
+ this.account.assert_expectations();
+
+ return monitor;
+ }
+
+}
diff --git a/test/meson.build b/test/meson.build
index 7c44a8b..b844d0d 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -20,6 +20,7 @@ geary_test_engine_sources = [
'engine/api/geary-attachment-test.vala',
'engine/api/geary-engine-test.vala',
'engine/app/app-conversation-test.vala',
+ 'engine/app/app-conversation-monitor-test.vala',
'engine/app/app-conversation-set-test.vala',
'engine/imap/command/imap-create-command-test.vala',
'engine/imap/response/imap-namespace-response-test.vala',
diff --git a/test/test-engine.vala b/test/test-engine.vala
index d5c06bc..ab4a0ef 100644
--- a/test/test-engine.vala
+++ b/test/test-engine.vala
@@ -28,6 +28,8 @@ int main(string[] args) {
engine.add_suite(new Geary.TimeoutManagerTest().get_suite());
engine.add_suite(new Geary.App.ConversationTest().get_suite());
engine.add_suite(new Geary.App.ConversationSetTest().get_suite());
+ // Depends on ConversationTest and ConversationSetTest passing
+ engine.add_suite(new Geary.App.ConversationMonitorTest().get_suite());
engine.add_suite(new Geary.HTML.UtilTest().get_suite());
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
engine.add_suite(new Geary.Imap.CreateCommandTest().get_suite());
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]