Merge branch 'mjog/imap-connection-fixes' into 'mainline'

IMAP connection fixes

See merge request GNOME/geary!479
This commit is contained in:
Michael Gratton 2020-03-30 11:35:18 +00:00
commit 297a59ca80
28 changed files with 1440 additions and 757 deletions

View file

@ -84,7 +84,7 @@ class Geary.ImapDB.AccountTest : TestCase {
new Imap.UIDValidity(7),
6 //unseen
),
new Imap.Capabilities(1)
new Imap.Capabilities.empty(0)
)
);
@ -123,7 +123,7 @@ class Geary.ImapDB.AccountTest : TestCase {
new Imap.UIDValidity(7),
6 //unseen
),
new Imap.Capabilities(1)
new Imap.Capabilities.empty(0)
)
);

View file

@ -0,0 +1,160 @@
/*
* Copyright 2019 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.Imap.ClientConnectionTest : TestCase {
private class TestCommand : Command {
public TestCommand() {
base("TEST");
}
}
private TestServer? server = null;
public ClientConnectionTest() {
base("Geary.Imap.ClientConnectionTest");
add_test("connect_disconnect", connect_disconnect);
if (GLib.Test.slow()) {
add_test("idle", idle);
add_test("command_timeout", command_timeout);
}
}
protected override void set_up() throws GLib.Error {
this.server = new TestServer();
}
protected override void tear_down() {
this.server.stop();
this.server = null;
}
public void connect_disconnect() throws GLib.Error {
var test_article = new ClientConnection(new_endpoint());
test_article.connect_async.begin(null, this.async_complete_full);
test_article.connect_async.end(async_result());
assert_non_null(test_article.get_remote_address());
assert_non_null(test_article.get_local_address());
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
assert_null(test_article.get_remote_address());
assert_null(test_article.get_local_address());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert(result.succeeded);
}
public void idle() throws GLib.Error {
this.server.add_script_line(RECEIVE_LINE, "a001 IDLE");
this.server.add_script_line(SEND_LINE, "+ idling");
this.server.add_script_line(RECEIVE_LINE, "DONE");
this.server.add_script_line(SEND_LINE, "a001 OK Completed");
this.server.add_script_line(RECEIVE_LINE, "a002 TEST");
this.server.add_script_line(SEND_LINE, "a002 OK Looks good");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
const int COMMAND_TIMEOUT = 1;
const int IDLE_TIMEOUT = 1;
var test_article = new ClientConnection(
new_endpoint(), COMMAND_TIMEOUT, IDLE_TIMEOUT
);
test_article.connect_async.begin(null, this.async_complete_full);
test_article.connect_async.end(async_result());
assert_false(test_article.is_in_idle(), "Initial idle state");
test_article.enable_idle_when_quiet(true);
assert_false(test_article.is_in_idle(), "Post-enabled idle state");
// Wait for idle to kick in
GLib.Timer timer = new GLib.Timer();
timer.start();
while (!test_article.is_in_idle() &&
timer.elapsed() < IDLE_TIMEOUT * 2) {
this.main_loop.iteration(false);
}
assert_true(test_article.is_in_idle(), "Entered idle");
// Ensure idle outlives command timeout
timer.start();
while (timer.elapsed() < COMMAND_TIMEOUT * 2) {
this.main_loop.iteration(false);
}
assert_true(test_article.is_in_idle(), "Post idle command timeout");
var command = new TestCommand();
test_article.send_command(command);
command.wait_until_complete.begin(null, this.async_complete_full);
command.wait_until_complete.end(async_result());
assert_false(test_article.is_in_idle(), "Post test command");
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert(result.succeeded);
}
public void command_timeout() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 TEST");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
const int TIMEOUT = 2;
bool sent = false;
bool recv_fail = false;
bool timed_out = false;
var test_article = new ClientConnection(new_endpoint(), TIMEOUT);
test_article.sent_command.connect(() => { sent = true; });
test_article.receive_failure.connect(() => { recv_fail = true; });
test_article.connect_async.begin(null, this.async_complete_full);
test_article.connect_async.end(async_result());
var command = new TestCommand();
command.response_timed_out.connect(() => { timed_out = true; });
test_article.send_command(command);
GLib.Timer timer = new GLib.Timer();
timer.start();
while (!timed_out && timer.elapsed() < TIMEOUT * 2) {
this.main_loop.iteration(false);
}
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
assert_true(sent, "connection.sent_command");
assert_true(recv_fail, "command.receive_failure");
assert_true(timed_out, "command.response_timed_out");
debug("Waiting for server...");
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(result.succeeded);
}
protected Endpoint new_endpoint() {
return new Endpoint(this.server.get_client_address(), NONE, 10);
}
}

View file

@ -0,0 +1,415 @@
/*
* Copyright 2019 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.Imap.ClientSessionTest : TestCase {
private const uint CONNECT_TIMEOUT = 2;
private TestServer? server = null;
public ClientSessionTest() {
base("Geary.Imap.ClientSessionTest");
add_test("connect_disconnect", connect_disconnect);
add_test("connect_with_capabilities", connect_with_capabilities);
if (GLib.Test.slow()) {
add_test("connect_timeout", connect_timeout);
}
add_test("login", login);
add_test("login_with_capabilities", login_with_capabilities);
add_test("logout", logout);
add_test("login_logout", login_logout);
add_test("initiate_request_capabilities", initiate_request_capabilities);
add_test("initiate_implicit_capabilities", initiate_implicit_capabilities);
add_test("initiate_namespace", initiate_namespace);
}
protected override void set_up() throws GLib.Error {
this.server = new TestServer();
}
protected override void tear_down() {
this.server.stop();
this.server = null;
}
public void connect_disconnect() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void connect_with_capabilities() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK [CAPABILITY IMAP4rev1] localhost test server ready"
);
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.capabilities.supports_imap4rev1());
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(result.succeeded);
}
public void connect_timeout() throws GLib.Error {
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
GLib.Timer timer = new GLib.Timer();
timer.start();
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
try {
test_article.connect_async.end(async_result());
assert_not_reached();
} catch (GLib.IOError.TIMED_OUT err) {
assert_double(timer.elapsed(), CONNECT_TIMEOUT, CONNECT_TIMEOUT * 0.5);
}
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(result.succeeded);
}
public void login_with_capabilities() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
this.server.add_script_line(
SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1] ohhai"
);
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
test_article.login_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.login_async.end(async_result());
assert_true(test_article.capabilities.supports_imap4rev1());
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void login() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
this.server.add_script_line(SEND_LINE, "a001 OK ohhai");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.login_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.login_async.end(async_result());
assert_true(test_article.get_protocol_state() == AUTHORIZED);
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void logout() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 logout");
this.server.add_script_line(SEND_LINE, "* BYE fine");
this.server.add_script_line(SEND_LINE, "a001 OK laters");
this.server.add_script_line(DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.logout_async.begin(null, this.async_complete_full);
test_article.logout_async.end(async_result());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void login_logout() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
this.server.add_script_line(SEND_LINE, "a001 OK ohhai");
this.server.add_script_line(RECEIVE_LINE, "a002 logout");
this.server.add_script_line(SEND_LINE, "* BYE fine");
this.server.add_script_line(SEND_LINE, "a002 OK laters");
this.server.add_script_line(DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.login_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.login_async.end(async_result());
assert_true(test_article.get_protocol_state() == AUTHORIZED);
test_article.logout_async.begin(null, this.async_complete_full);
test_article.logout_async.end(async_result());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void initiate_request_capabilities() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 capability");
this.server.add_script_line(SEND_LINE, "* CAPABILITY IMAP4rev1 LOGIN");
this.server.add_script_line(SEND_LINE, "a001 OK enjoy");
this.server.add_script_line(RECEIVE_LINE, "a002 login test password");
this.server.add_script_line(SEND_LINE, "a002 OK ohhai");
this.server.add_script_line(RECEIVE_LINE, "a003 capability");
this.server.add_script_line(SEND_LINE, "* CAPABILITY IMAP4rev1");
this.server.add_script_line(SEND_LINE, "a003 OK thanks");
this.server.add_script_line(RECEIVE_LINE, "a004 LIST \"\" INBOX");
this.server.add_script_line(SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox");
this.server.add_script_line(SEND_LINE, "a004 OK there");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.initiate_session_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.initiate_session_async.end(async_result());
assert_true(test_article.capabilities.supports_imap4rev1());
assert_false(test_article.capabilities.has_capability("AUTH"));
assert_int(2, test_article.capabilities.revision);
assert_string("Inbox", test_article.inbox.mailbox.name);
assert_true(test_article.inbox.mailbox.is_inbox);
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void initiate_implicit_capabilities() throws GLib.Error {
this.server.add_script_line(
SEND_LINE, "* OK [CAPABILITY IMAP4rev1 LOGIN] localhost test server ready"
);
this.server.add_script_line(RECEIVE_LINE, "a001 login test password");
this.server.add_script_line(SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1] ohhai");
this.server.add_script_line(RECEIVE_LINE, "a002 LIST \"\" INBOX");
this.server.add_script_line(SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox");
this.server.add_script_line(SEND_LINE, "a002 OK there");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.initiate_session_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.initiate_session_async.end(async_result());
assert_true(test_article.capabilities.supports_imap4rev1());
assert_false(test_article.capabilities.has_capability("AUTH"));
assert_int(2, test_article.capabilities.revision);
assert_string("Inbox", test_article.inbox.mailbox.name);
assert_true(test_article.inbox.mailbox.is_inbox);
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
public void initiate_namespace() throws GLib.Error {
this.server.add_script_line(
SEND_LINE,
"* OK [CAPABILITY IMAP4rev1 LOGIN] localhost test server ready"
);
this.server.add_script_line(
RECEIVE_LINE, "a001 login test password"
);
this.server.add_script_line(
SEND_LINE, "a001 OK [CAPABILITY IMAP4rev1 NAMESPACE] ohhai"
);
this.server.add_script_line(
RECEIVE_LINE, "a002 LIST \"\" INBOX"
);
this.server.add_script_line(
SEND_LINE, "* LIST (\\HasChildren) \".\" Inbox"
);
this.server.add_script_line(
SEND_LINE, "a002 OK there"
);
this.server.add_script_line(
RECEIVE_LINE, "a003 NAMESPACE"
);
this.server.add_script_line(
SEND_LINE,
"""* NAMESPACE (("INBOX." ".")) (("user." ".")) (("shared." "."))"""
);
this.server.add_script_line(SEND_LINE, "a003 OK there");
this.server.add_script_line(WAIT_FOR_DISCONNECT, "");
var test_article = new ClientSession(new_endpoint());
assert_true(test_article.get_protocol_state() == NOT_CONNECTED);
test_article.connect_async.begin(
CONNECT_TIMEOUT, null, this.async_complete_full
);
test_article.connect_async.end(async_result());
assert_true(test_article.get_protocol_state() == UNAUTHORIZED);
test_article.initiate_session_async.begin(
new Credentials(PASSWORD, "test", "password"),
null,
this.async_complete_full
);
test_article.initiate_session_async.end(async_result());
assert_int(1, test_article.get_personal_namespaces().size);
assert_string(
"INBOX.", test_article.get_personal_namespaces()[0].prefix
);
assert_int(1, test_article.get_shared_namespaces().size);
assert_string(
"shared.", test_article.get_shared_namespaces()[0].prefix
);
assert_int(1, test_article.get_other_users_namespaces().size);
assert_string(
"user.", test_article.get_other_users_namespaces()[0].prefix
);
test_article.disconnect_async.begin(null, this.async_complete_full);
test_article.disconnect_async.end(async_result());
TestServer.Result result = this.server.wait_for_script(this.main_loop);
assert_true(
result.succeeded,
result.error != null ? result.error.message : "Server result failed"
);
}
protected Endpoint new_endpoint() {
return new Endpoint(this.server.get_client_address(), NONE, 10);
}
}

View file

@ -265,7 +265,7 @@ class Geary.Imap.DeserializerTest : TestCase {
this.stream.add_data(bye.data);
bool eos = false;
this.deser.eos.connect(() => { eos = true; });
this.deser.end_of_stream.connect(() => { eos = true; });
this.process.begin(Expect.MESSAGE, (obj, ret) => { async_complete(ret); });
RootParameters? message = this.process.end(async_result());
@ -283,7 +283,7 @@ class Geary.Imap.DeserializerTest : TestCase {
this.deser.parameters_ready.connect((param) => { message = param; });
this.deser.bytes_received.connect((count) => { bytes_received += count; });
this.deser.eos.connect((param) => { eos = true; });
this.deser.end_of_stream.connect((param) => { eos = true; });
this.deser.deserialize_failure.connect(() => { deserialize_failure = true; });
this.deser.receive_failure.connect((err) => { receive_failure = true;});

View file

@ -87,7 +87,7 @@ class Geary.TimeoutManagerTest : TestCase {
this.main_loop.iteration(true);
}
assert_epsilon(timer.elapsed(), 1.0, SECONDS_EPSILON);
assert_double(timer.elapsed(), 1.0, SECONDS_EPSILON);
}
public void milliseconds() throws Error {
@ -101,7 +101,7 @@ class Geary.TimeoutManagerTest : TestCase {
this.main_loop.iteration(true);
}
assert_epsilon(timer.elapsed(), 0.1, MILLISECONDS_EPSILON);
assert_double(timer.elapsed(), 0.1, MILLISECONDS_EPSILON);
}
public void repeat_forever() throws Error {
@ -118,11 +118,7 @@ class Geary.TimeoutManagerTest : TestCase {
}
timer.stop();
assert_epsilon(timer.elapsed(), 2.0, SECONDS_EPSILON * 2);
}
private inline void assert_epsilon(double actual, double expected, double epsilon) {
assert(actual + epsilon >= expected && actual - epsilon <= expected);
assert_double(timer.elapsed(), 2.0, SECONDS_EPSILON * 2);
}
}

View file

@ -33,7 +33,7 @@ class Integration.Imap.ClientSession : TestCase {
}
public override void tear_down() throws GLib.Error {
if (this.session.get_protocol_state(null) != NOT_CONNECTED) {
if (this.session.get_protocol_state() != NOT_CONNECTED) {
this.session.disconnect_async.begin(null, async_complete_full);
this.session.disconnect_async.end(async_result());
}
@ -41,7 +41,7 @@ class Integration.Imap.ClientSession : TestCase {
}
public void session_connect() throws GLib.Error {
this.session.connect_async.begin(null, async_complete_full);
this.session.connect_async.begin(2, null, async_complete_full);
this.session.connect_async.end(async_result());
this.session.disconnect_async.begin(null, async_complete_full);
@ -98,7 +98,7 @@ class Integration.Imap.ClientSession : TestCase {
}
private void do_connect() throws GLib.Error {
this.session.connect_async.begin(null, async_complete_full);
this.session.connect_async.begin(5, null, async_complete_full);
this.session.connect_async.end(async_result());
}

View file

@ -3,6 +3,7 @@ subdir('data')
geary_test_lib_sources = [
'mock-object.vala',
'test-case.vala',
'test-server.vala',
]
geary_test_engine_sources = [
@ -41,6 +42,8 @@ geary_test_engine_sources = [
'engine/imap/message/imap-mailbox-specifier-test.vala',
'engine/imap/parameter/imap-list-parameter-test.vala',
'engine/imap/response/imap-namespace-response-test.vala',
'engine/imap/transport/imap-client-connection-test.vala',
'engine/imap/transport/imap-client-session-test.vala',
'engine/imap/transport/imap-deserializer-test.vala',
'engine/imap-db/imap-db-account-test.vala',
'engine/imap-db/imap-db-attachment-test.vala',

View file

@ -96,6 +96,10 @@ public void assert_int64(int64 expected, int64 actual, string? context = null)
}
}
public void assert_double(double actual, double expected, double epsilon) {
assert(actual + epsilon >= expected && actual - epsilon <= expected);
}
public void assert_uint(uint expected, uint actual, string? context = null)
throws GLib.Error {
if (expected != actual) {

View file

@ -46,20 +46,28 @@ int main(string[] args) {
engine.add_suite(new Geary.Db.DatabaseTest().get_suite());
engine.add_suite(new Geary.Db.VersionedDatabaseTest().get_suite());
engine.add_suite(new Geary.HTML.UtilTest().get_suite());
// Other IMAP tests rely on DataFormat working, so test that first
// Other IMAP tests rely on these working, so test them first
engine.add_suite(new Geary.Imap.DataFormatTest().get_suite());
engine.add_suite(new Geary.Imap.CreateCommandTest().get_suite());
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
engine.add_suite(new Geary.Imap.FetchCommandTest().get_suite());
engine.add_suite(new Geary.Imap.ListParameterTest().get_suite());
engine.add_suite(new Geary.Imap.MailboxSpecifierTest().get_suite());
engine.add_suite(new Geary.Imap.NamespaceResponseTest().get_suite());
// Depends on IMAP commands working
engine.add_suite(new Geary.Imap.DeserializerTest().get_suite());
engine.add_suite(new Geary.Imap.ClientConnectionTest().get_suite());
engine.add_suite(new Geary.Imap.ClientSessionTest().get_suite());
engine.add_suite(new Geary.ImapDB.AccountTest().get_suite());
engine.add_suite(new Geary.ImapDB.AttachmentTest().get_suite());
engine.add_suite(new Geary.ImapDB.AttachmentIoTest().get_suite());
engine.add_suite(new Geary.ImapDB.DatabaseTest().get_suite());
engine.add_suite(new Geary.ImapDB.EmailIdentifierTest().get_suite());
engine.add_suite(new Geary.ImapDB.FolderTest().get_suite());
engine.add_suite(new Geary.ImapEngine.AccountProcessorTest().get_suite());
engine.add_suite(new Geary.ImapEngine.GenericAccountTest().get_suite());

222
test/test-server.vala Normal file
View file

@ -0,0 +1,222 @@
/*
* Copyright 2019 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.
*/
/**
* A simple mock server for testing network connections.
*
* To use it, unit tests should construct an instance as a fixture in
* set up, specify a test script by adding lines and then check the
* result, before stopping the server in tear down.
*/
public class TestServer : GLib.Object {
/** Possible actions a script may take. */
public enum Action {
/**
* The implicit first action.
*
* This does not need to be specified as a script action, it
* will always be taken when a client connects.
*/
CONNECTED,
/** Send a line to the client. */
SEND_LINE,
/** Receive a line from the client. */
RECEIVE_LINE,
/** Wait for the client to disconnect. */
WAIT_FOR_DISCONNECT,
/** Disconnect immediately. */
DISCONNECT;
}
/** A line of the server's script. */
public struct Line {
/** The action to take for this line. */
public Action action;
/**
* The value for the action.
*
* If sending, this string will be sent. If receiving, the
* expected line.
*/
public string value;
}
/** The result of executing a script line. */
public struct Result {
/** The expected action. */
public Line line;
/** Was the expected action successful. */
public bool succeeded;
/** The actual string sent by a client when not as expected. */
public string? actual;
/** In case of an error being thrown, the error itself. */
public GLib.Error? error;
}
private GLib.DataStreamNewlineType line_ending;
private uint16 port;
private GLib.ThreadedSocketService service =
new GLib.ThreadedSocketService(10);
private GLib.Cancellable running = new GLib.Cancellable();
private Gee.List<Line?> script = new Gee.ArrayList<Line?>();
private GLib.AsyncQueue<Result?> completion_queue =
new GLib.AsyncQueue<Result?>();
public TestServer(GLib.DataStreamNewlineType line_ending = CR_LF)
throws GLib.Error {
this.line_ending = line_ending;
this.port = this.service.add_any_inet_port(null);
this.service.run.connect((conn) => {
handle_connection(conn);
return true;
});
this.service.start();
}
public GLib.SocketConnectable get_client_address() {
return new GLib.NetworkAddress("localhost", this.port);
}
public void add_script_line(Action action, string value) {
this.script.add({ action, value });
}
public Result wait_for_script(GLib.MainContext loop) {
Result? result = null;
while (result == null) {
loop.iteration(false);
result = this.completion_queue.try_pop();
}
return result;
}
public void stop() {
this.service.stop();
this.running.cancel();
}
private void handle_connection(GLib.SocketConnection connection) {
debug("Connected");
var input = new GLib.DataInputStream(
connection.input_stream
);
input.set_newline_type(this.line_ending);
var output = new GLib.DataOutputStream(
connection.output_stream
);
Line connected_line = { CONNECTED, "" };
Result result = { connected_line, true, null, null };
foreach (var line in this.script) {
result.line = line;
switch (line.action) {
case SEND_LINE:
debug("Sending: %s", line.value);
try {
output.put_string(line.value);
switch (this.line_ending) {
case CR:
output.put_byte('\r');
break;
case LF:
output.put_byte('\n');
break;
default:
output.put_byte('\r');
output.put_byte('\n');
break;
}
} catch (GLib.Error err) {
result.succeeded = false;
result.error = err;
}
break;
case RECEIVE_LINE:
debug("Waiting for: %s", line.value);
try {
size_t len;
string? received = input.read_line(out len, this.running);
if (received == null || received != line.value) {
result.succeeded = false;
result.actual = received;
}
} catch (GLib.Error err) {
result.succeeded = false;
result.error = err;
}
break;
case WAIT_FOR_DISCONNECT:
debug("Waiting for disconnect");
var socket = connection.get_socket();
try {
uint8 buffer[4096];
while (socket.receive_with_blocking(buffer, true) > 0) { }
} catch (GLib.Error err) {
result.succeeded = false;
result.error = err;
}
break;
case DISCONNECT:
debug("Disconnecting");
try {
connection.close(this.running);
} catch (GLib.Error err) {
result.succeeded = false;
result.error = err;
}
break;
}
if (!result.succeeded) {
break;
}
}
if (result.succeeded) {
debug("Done");
} else if (result.error != null) {
warning("Error: %s", result.error.message);
} else if (result.line.action == RECEIVE_LINE) {
warning("Received unexpected line: %s", result.actual ?? "(null)");
} else {
warning("Failed for unknown reason");
}
if (connection.is_connected()) {
try {
connection.close(this.running);
} catch (GLib.Error err) {
warning(
"Error closing test server connection: %s", err.message
);
}
}
this.completion_queue.push(result);
}
}