From cddbb28a4388424a7b8b598e5f4b44328a073cc9 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 31 Mar 2019 01:42:08 +1100 Subject: [PATCH] Add initial integration test famework and IMAP client session tests --- test/README.md | 53 +++++++++++ test/integration/imap/client-session.vala | 99 ++++++++++++++++++++ test/meson.build | 27 +++++- test/test-integration.vala | 107 ++++++++++++++++++++++ 4 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 test/README.md create mode 100644 test/integration/imap/client-session.vala create mode 100644 test/test-integration.vala diff --git a/test/README.md b/test/README.md new file mode 100644 index 00000000..2f634f39 --- /dev/null +++ b/test/README.md @@ -0,0 +1,53 @@ + +Automated Test Infrastructure +============================= + +Geary currently supports three types of automated tests: + + * Engine unit tests + * Client (GTK and JavaScript) unit tests + * Server integration tests + +Unit tests +---------- + +Unit tests test individual functions, in general avoid doing any I/O +so they are fast, and can be run automatically. + +The engine and client unit tests are hooked up to the Meson build, so +you can use Meson's test infrastructure to build and run them. These +are run automatically as part of the Gitlab CI process and if you use +the development Makefile, you can execute them locally by simply +calling: + + make test + +The engine tests can be run headless (i.e. without an X11 or Wayland +session), but the client tests require a functioning display since +they execute GTK code. + +Integration tests +----------------- + +Integration tests run Geary's network code against actual servers, to +ensure that the code also works in the real world. + +The integration tests are built by default, but not currently hooked +up to Meson and are not automatically run by Gitlab CI, since they +require multiple working servers, network connection to the servers, +and login credentials. + +You can run them manually however against any server you have a test +account on, using the following form: + + build/test/test-integration PROTOCOL PROVIDER [HOSTNAME] LOGIN PASSWORD + +For example, to test against GMail's IMAP service: + + build/test/test-integration imap gmail test@gmail.com p455w04d + +If `PROVIDER` is `other`, then `HOSTNAME` is required. + +The easiest way to test against a number of different servers at the +moment is to create a test account for each, then write a shell script +or similar to execute the tests against each in turn. diff --git a/test/integration/imap/client-session.vala b/test/integration/imap/client-session.vala new file mode 100644 index 00000000..6cf5ec2e --- /dev/null +++ b/test/integration/imap/client-session.vala @@ -0,0 +1,99 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +class Integration.Imap.ClientSession : TestCase { + + + private Configuration config; + private Geary.Imap.ClientSession? session; + + + public ClientSession(Configuration config) { + base("Integration.Imap.ClientSession"); + this.config = config; + add_test("session_connect", session_connect); + + add_test("login_password_invalid", login_password_invalid); + if (config.provider == GMAIL || + config.provider == OUTLOOK) { + add_test("login_oauth2_invalid", login_oauth2_invalid); + } + + add_test("initiate_session", initiate_session); + } + + public override void set_up() { + this.session = new Geary.Imap.ClientSession( + this.config.target + ); + } + + public override void tear_down() throws GLib.Error { + if (this.session.get_protocol_state(null) != UNCONNECTED) { + this.session.disconnect_async.begin(null, async_complete_full); + this.session.disconnect_async.end(async_result()); + } + this.session = null; + } + + public void session_connect() throws GLib.Error { + this.session.connect_async.begin(null, async_complete_full); + this.session.connect_async.end(async_result()); + + this.session.disconnect_async.begin(null, async_complete_full); + this.session.disconnect_async.end(async_result()); + } + + public void login_password_invalid() throws GLib.Error { + do_connect(); + + Geary.Credentials password_creds = new Geary.Credentials( + PASSWORD, "automated-integration-test", "password" + ); + this.session.login_async.begin( + password_creds, null, async_complete_full + ); + try { + this.session.login_async.end(async_result()); + assert_not_reached(); + } catch (Geary.ImapError.UNAUTHENTICATED err) { + // All good + } + } + + public void login_oauth2_invalid() throws GLib.Error { + do_connect(); + + Geary.Credentials oauth2_creds = new Geary.Credentials( + OAUTH2, "automated-integration-test", "password" + ); + this.session.login_async.begin( + oauth2_creds, null, async_complete_full + ); + try { + this.session.login_async.end(async_result()); + assert_not_reached(); + } catch (Geary.ImapError.UNAUTHENTICATED err) { + // All good + } + } + + public void initiate_session() throws GLib.Error { + do_connect(); + + this.session.initiate_session_async.begin( + this.config.credentials, null, async_complete_full + ); + this.session.initiate_session_async.end(async_result()); + } + + private void do_connect() throws GLib.Error { + this.session.connect_async.begin(null, async_complete_full); + this.session.connect_async.end(async_result()); + } + +} diff --git a/test/meson.build b/test/meson.build index ab3ea78c..4f7f7554 100644 --- a/test/meson.build +++ b/test/meson.build @@ -87,6 +87,12 @@ geary_test_client_sources = [ geary_resources ] +geary_test_integration_sources = [ + 'test-integration.vala', + + 'integration/imap/client-session.vala', +] + geary_test_lib_dependencies = [ gee, gio, @@ -103,7 +109,7 @@ geary_test_client_dependencies = [ ] geary_test_client_dependencies += geary_client_dependencies -geary_test_lib = static_library('geary-test-lib', +geary_test_lib = static_library('test-lib', geary_test_lib_sources, dependencies: geary_test_lib_dependencies, include_directories: config_h_dir, @@ -111,7 +117,7 @@ geary_test_lib = static_library('geary-test-lib', c_args: geary_c_options, ) -geary_test_engine_bin = executable('geary-test-engine', +geary_test_engine_bin = executable('test-engine', geary_test_engine_sources, link_with: geary_test_lib, dependencies: geary_test_engine_dependencies, @@ -120,7 +126,7 @@ geary_test_engine_bin = executable('geary-test-engine', c_args: geary_c_options, ) -geary_test_client_bin = executable('geary-test-client', +geary_test_client_bin = executable('test-client', geary_test_client_sources, dependencies: geary_test_client_dependencies, link_with: geary_test_lib, @@ -129,5 +135,20 @@ geary_test_client_bin = executable('geary-test-client', c_args: geary_c_options, ) +geary_test_integration_bin = executable('test-integration', + geary_test_integration_sources, + dependencies: [ + geary_engine_dep, + gee, + gio, + gmime, + webkit2gtk, + ], + link_with: geary_test_lib, + include_directories: config_h_dir, + vala_args: geary_vala_options, + c_args: geary_c_options, +) + test('engine-tests', geary_test_engine_bin) test('client-tests', geary_test_client_bin) diff --git a/test/test-integration.vala b/test/test-integration.vala new file mode 100644 index 00000000..8204275f --- /dev/null +++ b/test/test-integration.vala @@ -0,0 +1,107 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + + +private const int TIMEOUT = 5; + + +public struct Integration.Configuration { + + Geary.Protocol type; + Geary.ServiceProvider provider; + Geary.ServiceInformation service; + Geary.Endpoint target; + Geary.Credentials credentials; + +} + + +int main(string[] args) { + /* + * Initialise all the things. + */ + + Test.init(ref args); + + Geary.RFC822.init(); + Geary.HTML.init(); + Geary.Logging.init(); + Geary.Logging.log_to(stderr); + GLib.Log.set_default_handler(Geary.Logging.default_handler); + + Integration.Configuration config = load_config(args); + + /* + * Hook up all tests into appropriate suites + */ + + TestSuite integration = new TestSuite("integration"); + + switch (config.type) { + case IMAP: + integration.add_suite( + new Integration.Imap.ClientSession(config).get_suite() + ); + break; + } + + /* + * Run the tests + */ + TestSuite root = TestSuite.get_root(); + root.add_suite(integration); + + MainLoop loop = new MainLoop(); + + int ret = -1; + Idle.add(() => { + ret = Test.run(); + loop.quit(); + return false; + }); + + loop.run(); + return ret; +} + +private Integration.Configuration load_config(string[] args) { + int i = 1; + try { + Geary.Protocol type = Geary.Protocol.for_value(args[i++]); + Geary.ServiceProvider provider = Geary.ServiceProvider.for_value( + args[i++] + ); + Geary.ServiceInformation service = new Geary.ServiceInformation( + type, provider + ); + + if (provider == OTHER) { + service.host = args[i++]; + service.port = service.get_default_port(); + } + + Geary.Credentials credentials = new Geary.Credentials( + PASSWORD, args[i++], args[i++] + ); + + provider.set_service_defaults(service); + + Geary.Endpoint target = new Geary.Endpoint( + new NetworkAddress(service.host, service.port), + service.transport_security, + TIMEOUT + ); + + return { type, provider, service, target, credentials }; + } catch (GLib.Error err) { + error( + "Error loading config: %s", + (new Geary.ErrorContext(err)).format_full_error() + ); + } + +} \ No newline at end of file