diff --git a/apps/kolab_guam/test/kolab_guam_session_SUITE.erl b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl index a5abd8c..0ca2a99 100644 --- a/apps/kolab_guam/test/kolab_guam_session_SUITE.erl +++ b/apps/kolab_guam/test/kolab_guam_session_SUITE.erl @@ -1,361 +1,370 @@ %% 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_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 }, + %handle_info + {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 = [] }), % Run without rules {<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = undefined, command_split_reset_trigger = [] %FIXME ? }} = kolab_guam_session:process_client_data(Socket, <<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>, State#state{ rules_deciding = [] }), % Activate filtering - {noreply, #state{ + {<<"y1 ID (\"name\" \"Test\")\r\n">>, #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 }), + }} = kolab_guam_session:process_client_data(Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>, State#state{ rules_deciding = ActiveRules }), % Append - {noreply, #state{ + {<<"y1 APPEND INBOX {30}\r\n0123">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {30}">>}, command_split_reset_trigger = reset_for_next_client_command - } = IntermediateState4} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 APPEND INBOX {30}\r\n0123">>}, State#state{ rules_deciding = ActiveRules }), + } = IntermediateState4} = kolab_guam_session:process_client_data(Socket, <<"y1 APPEND INBOX {30}\r\n0123">>, State#state{ rules_deciding = ActiveRules }), - {noreply, #state{ + {<<"456789">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {30}">>}, command_split_reset_trigger = reset_for_next_client_command - } = IntermediateState5} = kolab_guam_session:handle_info({tcp, Socket, <<"456789">>}, IntermediateState4), + } = IntermediateState5} = kolab_guam_session:process_client_data(Socket, <<"456789">>, IntermediateState4), - {noreply, #state{ + {<<"0123456789012345678">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {30}">>}, command_split_reset_trigger = reset_for_next_client_command - } = IntermediateState6} = kolab_guam_session:handle_info({tcp, Socket, <<"0123456789012345678">>}, IntermediateState5), + } = IntermediateState6} = kolab_guam_session:process_client_data(Socket, <<"0123456789012345678">>, IntermediateState5), - {noreply, #state{ + {<<"9\r\ny2 ENABLE QRESYNC\r\n">>, #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, <<"9\r\ny2 ENABLE QRESYNC\r\n">>}, IntermediateState6), - + }} = kolab_guam_session:process_client_data(Socket, <<"9\r\ny2 ENABLE QRESYNC\r\n">>, IntermediateState6), % Append2 - {noreply, #state{ + {<<"y1 APPEND INBOX {36}\r\n0123">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {36}">>}, - command_split_reset_trigger = reset_for_next_client_command - } = IntermediateState7} = kolab_guam_session:handle_info({tcp, Socket, <<"y1 APPEND INBOX {36}\r\n0123">>}, State#state{ rules_deciding = ActiveRules }), + command_split_reset_trigger = reset_for_next_client_command, + continuation_bytes = 32 + } = IntermediateState7} = kolab_guam_session:process_client_data(Socket, <<"y1 APPEND INBOX {36}\r\n0123">>, State#state{ rules_deciding = ActiveRules }), - {noreply, #state{ + {<<"456789\r\n0123456789\r\n0123456789">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {36}">>}, - command_split_reset_trigger = reset_for_next_client_command - } = IntermediateState8} = kolab_guam_session:handle_info({tcp, Socket, <<"456789\r\n0123456789\r\n0123456789">>}, IntermediateState7), + command_split_reset_trigger = reset_for_next_client_command, + continuation_bytes = 2 + } = IntermediateState8} = kolab_guam_session:process_client_data(Socket, <<"456789\r\n0123456789\r\n0123456789">>, IntermediateState7), - {noreply, #state{ + {<<"\r\n">>, #state{ server_config = ServerConfig, buffered_client_data = <<>>, %This ensures we aren't buffering during the continuation current_command_split = {<<"y1">>,<<"APPEND">>,<<"INBOX {36}">>}, - command_split_reset_trigger = reset_for_next_client_command - }} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, IntermediateState8), + command_split_reset_trigger = reset_for_next_client_command, + continuation_bytes = 0 + }} = kolab_guam_session:process_client_data(Socket, <<"\r\n">>, IntermediateState8), % Don't activate filtering - {noreply, #state{ + {<<"y1 ID (\"name\" \"Test/KOLAB\")\r\n">>, #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 }), + }} = kolab_guam_session:process_client_data(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{ + {<<>>, #state{ buffered_client_data = <<"y1">>, rules_active = [] } = IntermediateState1} = kolab_guam_session:process_client_data(Socket, <<"y1">>, State#state{ rules_deciding = ActiveRules }), + {<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>, #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), + }} = kolab_guam_session:process_client_data(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{ + {<<"\r\n">>, #state{ buffered_client_data = <<"y1 ">>, rules_active = [] } = IntermediateState2} = kolab_guam_session:process_client_data(Socket, <<"\r\ny1 ">>, State#state{ rules_deciding = ActiveRules }), + {<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\n">>, #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), + }} = kolab_guam_session:process_client_data(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{ + {<<"\r\n">>, #state{ buffered_client_data = <<>>, rules_active = [] } = IntermediateState3} = kolab_guam_session:process_client_data(Socket, <<"\r\n">>, State#state{ rules_deciding = ActiveRules }), + {<<"y1 ID (\"name\" \"Test\")\r\n">>, #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), + }} = kolab_guam_session:process_client_data(Socket, <<"y1 ID (\"name\" \"Test\")\r\n">>, IntermediateState3), % Activate with multi-line packet - {noreply, #state{ + {<<"y1 ID (\"name\" \"Test\")\r\ny2 ENABLE QRESYNC\r\ny3 LIST \"\" \"%\" RETURN (SUBSCRIBED))\r\n">>, #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 }), + }} = kolab_guam_session:process_client_data(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, _}] }} = 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 used to 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) + 36)), ExpectedCommandSplit = {<<"y1">>, <<"APPEND">>, list_to_binary([<<"INBOX {">>, BinaryCount, <<"}">>])}, S = os:timestamp(), % Append {noreply, #state{ server_config = ServerConfig, 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, 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, current_command_split = ExpectedCommandSplit, command_split_reset_trigger = reset_for_next_client_command } = IntermediateState6} = kolab_guam_session:handle_info({tcp, Socket, <<"\r\n">>}, IntermediateState5), {noreply, #state{ server_config = ServerConfig, current_command_split = ExpectedCommandSplit, command_split_reset_trigger = reset_for_next_client_command }} = kolab_guam_session:handle_info({tcp, Socket, Data}, IntermediateState6), E = os:timestamp(), X = timer:now_diff(E,S), lager:info("Runtime: ~p usecs", [X]), lager:info("Done").