diff --git a/apps/kolab_guam/test/kolab_guam_session_SUITE.erl b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl index 49b2b53..61bc533 100644 --- a/apps/kolab_guam/test/kolab_guam_session_SUITE.erl +++ b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl @@ -1,292 +1,358 @@ %% Copyright 2015 Kolab Systems AG (http://www.kolabsys.com) %% %% Christian Mollekopf (Kolab Systems) %% %% This program is free software: you can redistribute it and/or modify %% it under the terms of the GNU General Public License as published by %% the Free Software Foundation, either version 3 of the License, or %% (at your option) any later version. %% %% This program is distributed in the hope that it will be useful, %% but WITHOUT ANY WARRANTY; without even the implied warranty of %% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %% GNU General Public License for more details. %% %% You should have received a copy of the GNU General Public License %% along with this program. If not, see . -module(kolab_guam_session_SUITE). % easier than exporting by name -compile(export_all). % required for common_test to work -include_lib("common_test/include/ct.hrl"). -include("../src/kolab_guam_session.hrl"). %%%%%%%%%%%%%%%%%%%%%%%%%%% %% common test callbacks %% %%%%%%%%%%%%%%%%%%%%%%%%%%% % Specify a list of all unit test functions all() -> [ kolab_guam_session_test_client, - kolab_guam_session_test_server + kolab_guam_session_test_server, + kolab_guam_session_test_client_benchmark ]. % required, but can just return Config. this is a suite level setup function. init_per_suite(Config) -> Config. % required, but can just return Config. this is a suite level tear down function. end_per_suite(Config) -> Config. % optional, can do function level setup for all functions, % or for individual functions by matching on TestCase. init_per_testcase(_TestCase, Config) -> Config. % optional, can do function level tear down for all functions, % or for individual functions by matching on TestCase. end_per_testcase(_TestCase, Config) -> Config. kolab_guam_session_test_client(_TestConfig) -> %% setup boilerplate ServerConfig = kolab_guam_sup:default_imap_server_config(), { ok, ImapSession } = eimap:start_link(ServerConfig), { ok, Socket } = gen_tcp:listen(9964, [ { reuseaddr, true }, {active, false}, inet6 ]), lager:start(), lager:set_loglevel(lager_console_backend, debug), ActiveRules = [{ kolab_guam_rule_filter_groupware, kolab_guam_rule_filter_groupware:new({}) }], State = #state{ socket = Socket, server_config = ServerConfig, imap_session = ImapSession }, % Run without rules {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = undefined, command_split_reset_trigger = [] %FIXME ? }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>}, State#state{ rules_deciding = [] }), % Activate filtering {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>}, State#state{ rules_deciding = ActiveRules }), % Append {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {7}">>}, command_split_reset_trigger = reset_for_next_client_command } = IntermediateState4} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 APPEND INBOX {7}\r\n123">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {7}">>}, command_split_reset_trigger = reset_for_next_client_command } = IntermediateState5} = kolab_guam_session:handle_info({tcp, Socket, <<"456">>}, IntermediateState4), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y2">>,<<"ENABLE">>,<<"QRESYNC">>}, command_split_reset_trigger = reset_for_next_client_command }} = kolab_guam_session:handle_info({tcp, Socket, <<"7\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState5), % Append2 {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {32}">>}, command_split_reset_trigger = reset_for_next_client_command } = IntermediateState6} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 APPEND INBOX {32}\r\n0123">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {32}">>}, command_split_reset_trigger = reset_for_next_client_command } = IntermediateState7} = kolab_guam_session:handle_info({tcp, Socket, <<"456789\r\n0123456789\r\n0123456789">>}, IntermediateState6), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {32}">>}, command_split_reset_trigger = reset_for_next_client_command }} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, IntermediateState7), % Don't activate filtering {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test/KOLAB\")">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [] }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test/KOLAB\")\r\n">>}, State#state{ rules_deciding = ActiveRules }), % Lone tag in a packet. Can/Could happen with iPhone apparently. (See commit 89f9dc93757c68032ed17f42838858bdfaefa408) {noreply, #state{ buffered_client_data = <<"y1">>, rules_active = [] } = IntermediateState1} = kolab_guam_session:handle_info({tcp, Socket, <<"y1">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y2">>,<<"ENABLE">>,<<"QRESYNC">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] }} = kolab_guam_session:handle_info({tcp, Socket, <<" ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState1), % Lone tag in a packet, but with a \r\n before. See guam-0.9.2-stalling-client-buffer-and-split-command-handling.patch (This triggers the odd List buffering case) {noreply, #state{ buffered_client_data = <<"y1 ">>, rules_active = [] } = IntermediateState2} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\ny1 ">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y2">>,<<"ENABLE">>,<<"QRESYNC">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] }} = kolab_guam_session:handle_info({tcp, Socket, <<"ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState2), % Empty line (should be considered a complete command and should not be buffered) {noreply, #state{ buffered_client_data = <<>>, rules_active = [] } = IntermediateState3} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>}, IntermediateState3), % Activate with multi-line packet {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y3">>,<<"LIST">>,<<"\"\" \"%\" RETURN (SUBSCRIBED))">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], % Make sure that we have processed y3 in here (and don't stop processing at y1) rules_active = [{kolab_guam_rule_filter_groupware, {state,undefined,<<"y3">>,true,<<>>,[<<"LIST">>,<<"list">>,<<"XLIST">>,<<"xlist">>,<<"LSUB">>,<<"lsub">>]}}] % rules_active = [{kolab_guam_rule_filter_groupware, _}] }} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>}, State#state{ rules_deciding = ActiveRules }), % Test various packet splits PacketsSplits = [ [<<"y1 ID (\"name\" \"Test\")\r\n">>, <<"y2 ENABLE QRESYNC\r\n">>, <<"y3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>], [<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>], [<<"y1 ID (\"name\" \"Test\")">>, <<"\r\ny2">>, <<" ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>], [<<"y1 ID (\"name\" \"Test\")">>, <<"\r\n">>, <<"y2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>], [<<"y1">>, <<" ">>, <<"ID">>, <<" (\"name\" \"Test\")\r\n">>, <<"y2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>] ], lists:foldl(fun(Packets, _) -> try_packets(Packets, Socket, ServerConfig, State#state{ rules_deciding = ActiveRules }) end, undefined, PacketsSplits), lager:info("Done"). try_packets(Packets, Socket, ServerConfig, State) -> #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y3">>,<<"LIST">>,<<"\"\" \"%\" RETURN (SUBSCRIBED))">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], % Make sure that we have processed y3 in here (and don't stop processing at y1) rules_active = [{kolab_guam_rule_filter_groupware, {state,undefined,<<"y3">>,true,<<>>,[<<"LIST">>,<<"list">>,<<"XLIST">>,<<"xlist">>,<<"LSUB">>,<<"lsub">>]}}] } = lists:foldl(fun(Packet, _State) -> {_, NewState} = kolab_guam_session:handle_info({tcp, Socket, Packet}, _State), NewState end, State, Packets). kolab_guam_session_test_server(_TestConfig) -> %% setup boilerplate % gen_tcp:connect ServerConfig = kolab_guam_sup:default_imap_server_config(), { ok, ImapSession } = eimap:start_link(ServerConfig), { ok, Socket } = gen_tcp:listen(9964, [ { reuseaddr, true }, {active, false}, inet6 ]), lager:start(), lager:set_loglevel(lager_console_backend, debug), ActiveRules = [{ kolab_guam_rule_filter_groupware, kolab_guam_rule_filter_groupware:new({}) }], State = #state{ socket = Socket, server_config = ServerConfig, imap_session = ImapSession }, % This should trigger a metadata request {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, % current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>}, current_command_split = {<<"y3">>,<<"LIST">>,<<"\"\" \"%\" RETURN (SUBSCRIBED))">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] } = IntermediateState1} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>}, State#state{ rules_deciding = ActiveRules }), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, % current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>}, current_command_split = undefined, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] } = IntermediateState2} = kolab_guam_session:handle_info({imap_server_response, <<"* ID (\"name\" \"Cyrus IMAPD\")\n\ry1 OK Completed\r\n">>}, IntermediateState1), {noreply, #state{ server_config = ServerConfig, buffered_client_data = <<>>, % current_command_split = {<<"y1">>,<<"ID">>,<<"(\"name\" \"Test\")">>}, command_split_reset_trigger = reset_for_next_client_command, rules_deciding = [], rules_active = [{kolab_guam_rule_filter_groupware, _}] - } = IntermediateState3} = kolab_guam_session:handle_info({imap_server_response, <<"* METADATA INBOX (/shared/vendor/kolab/folder-type \"mail\")\r\n">>}, IntermediateState2), + }} = kolab_guam_session:handle_info({imap_server_response, <<"* METADATA INBOX (/shared/vendor/kolab/folder-type \"mail\")\r\n">>}, IntermediateState2), % <<"* METADATA Archive (/shared/vendor/kolab/folder-type NIL)\r\n"> % <<"* METADATA Calendar (/shared/vendor/kolab/folder-type \"event\")\r\n"> % <<"EG0001 OK Completed\r\n">> lager:info("Done"). + + +kolab_guam_session_test_client_benchmark(_TestConfig) -> + %% setup boilerplate + ServerConfig = kolab_guam_sup:default_imap_server_config(), + { ok, ImapSession } = eimap:start_link(ServerConfig), + { ok, Socket } = gen_tcp:listen(9964, [ { reuseaddr, true }, {active, false}, inet6 ]), + + lager:start(), + lager:set_loglevel(lager_console_backend, debug), + + ActiveRules = [{ kolab_guam_rule_filter_groupware, kolab_guam_rule_filter_groupware:new({}) }], + + State = #state{ + socket = Socket, + server_config = ServerConfig, + imap_session = ImapSession + }, + + Count = 70, + % We have a factor of 10 in runtime between \r\n (causing line splits), and just \n (not causing line splits) + Payload = <<"0123456789\r\n">>, + Data = list_to_binary(lists:duplicate(Count, Payload)), + BinaryCount = list_to_binary(integer_to_list(Count * size(Payload) + 32)), + ExpectedCommandSplit = {<<"y1">>, <<"APPEND">>, list_to_binary([<<"INBOX {">>, BinaryCount, <<"}">>])}, + + S = os:timestamp(), + + % Append + {noreply, #state{ + server_config = ServerConfig, + buffered_client_data = <<>>, + current_command_split = ExpectedCommandSplit, + command_split_reset_trigger = reset_for_next_client_command + } = IntermediateState4} = kolab_guam_session:handle_info({tcp, Socket, list_to_binary([<<"y1 APPEND INBOX {">>, BinaryCount, <<"}\r\n0123">>])}, State#state{ rules_deciding = ActiveRules }), + + {noreply, #state{ + server_config = ServerConfig, + buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation + current_command_split = ExpectedCommandSplit, + command_split_reset_trigger = reset_for_next_client_command + } = IntermediateState5} = kolab_guam_session:handle_info({tcp, Socket, <<"456789\r\n0123456789\r\n0123456789">>}, IntermediateState4), + + + {noreply, #state{ + server_config = ServerConfig, + buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation + current_command_split = ExpectedCommandSplit, + command_split_reset_trigger = reset_for_next_client_command + } = IntermediateState6} = kolab_guam_session:handle_info({tcp, Socket, Data}, IntermediateState5), + + {noreply, #state{ + server_config = ServerConfig, + buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation + current_command_split = ExpectedCommandSplit, + command_split_reset_trigger = reset_for_next_client_command + }} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, IntermediateState6), + + + E = os:timestamp(), + X = timer:now_diff(E,S), + lager:info("Runtime: ~p usecs", [X]), + + + lager:info("Done").