1%%% File    : ibrowse_tests.erl
2%%% Authors : Benjamin Lee <http://github.com/benjaminplee>
3%%%           Dan Schwabe <http://github.com/dfschwabe>
4%%%           Brian Richards <http://github.com/richbria>
5%%% Description : Functional tests of the ibrowse library using a live test HTTP server
6%%% Created : 18 November 2014 by Benjamin Lee <yardspoon@gmail.com>
7
8-module(ibrowse_tests).
9
10-include_lib("eunit/include/eunit.hrl").
11-define(PER_TEST_TIMEOUT_SEC, 60).
12-define(TIMEDTEST(Desc, Fun), {Desc, {timeout, ?PER_TEST_TIMEOUT_SEC, fun Fun/0}}).
13
14-define(SERVER_PORT, 8181).
15-define(BASE_URL, "http://localhost:" ++ integer_to_list(?SERVER_PORT)).
16-define(SHORT_TIMEOUT_MS, 5000).
17-define(LONG_TIMEOUT_MS, 30000).
18-define(PAUSE_FOR_CONNECTIONS_MS, 2000).
19
20-compile(export_all).
21
22setup() ->
23    application:start(crypto),
24    application:start(public_key),
25    application:start(ssl),
26    ibrowse_test_server:start_server(?SERVER_PORT, tcp),
27    ibrowse:start(),
28    ok.
29
30teardown(_) ->
31    ibrowse:stop(),
32    ibrowse_test_server:stop_server(?SERVER_PORT),
33    ok.
34
35running_server_fixture_test_() ->
36    {foreach,
37     fun setup/0,
38     fun teardown/1,
39     [
40        ?TIMEDTEST("Simple request can be honored", simple_request),
41        ?TIMEDTEST("Slow server causes timeout", slow_server_timeout),
42        ?TIMEDTEST("Pipeline depth goes down with responses", pipeline_depth),
43        ?TIMEDTEST("Pipelines refill", pipeline_refill),
44        ?TIMEDTEST("Timeout closes pipe", closing_pipes),
45        ?TIMEDTEST("Requests are balanced over connections", balanced_connections),
46        ?TIMEDTEST("Pipeline too small signals retries", small_pipeline),
47        ?TIMEDTEST("Dest status can be gathered", status)
48     ]
49    }.
50
51simple_request() ->
52    ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [])).
53
54slow_server_timeout() ->
55    ?assertMatch({error, req_timedout}, ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [], 5000)).
56
57pipeline_depth() ->
58    MaxSessions = 2,
59    MaxPipeline = 2,
60    RequestsSent = 2,
61    EmptyPipelineDepth = 0,
62
63    ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
64
65    Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
66    times(RequestsSent, fun() -> spawn_link(Fun) end),
67
68    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
69
70    Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
71    ?assertEqual(MaxSessions, length(Counts)),
72    ?assertEqual(lists:duplicate(MaxSessions, EmptyPipelineDepth), Counts).
73
74pipeline_refill() ->
75    MaxSessions = 2,
76    MaxPipeline = 2,
77    RequestsToFill = MaxSessions * MaxPipeline,
78
79    %% Send off enough requests to fill sessions and pipelines in rappid succession
80    Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
81    times(RequestsToFill, fun() -> spawn_link(Fun) end),
82    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
83
84    % Verify that connections properly reported their completed responses and can still accept more
85    ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)),
86
87    % and do it again to make sure we really are clear
88    times(RequestsToFill, fun() -> spawn_link(Fun) end),
89    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
90
91    % Verify that connections properly reported their completed responses and can still accept more
92    ?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)).
93
94closing_pipes() ->
95    MaxSessions = 2,
96    MaxPipeline = 2,
97    RequestsSent = 2,
98    BalancedNumberOfRequestsPerConnection = 1,
99
100    ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
101
102    Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
103    times(RequestsSent, fun() -> spawn_link(Fun) end),
104
105    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
106
107    Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
108    ?assertEqual(MaxSessions, length(Counts)),
109    ?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts),
110
111    timer:sleep(?SHORT_TIMEOUT_MS),
112
113    ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()).
114
115balanced_connections() ->
116    MaxSessions = 4,
117    MaxPipeline = 100,
118    RequestsSent = 80,
119    BalancedNumberOfRequestsPerConnection = 20,
120
121    ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
122
123    Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?LONG_TIMEOUT_MS) end,
124    times(RequestsSent, fun() -> spawn_link(Fun) end),
125
126    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
127
128    Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
129    ?assertEqual(MaxSessions, length(Counts)),
130
131    ?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts).
132
133small_pipeline() ->
134    MaxSessions = 10,
135    MaxPipeline = 10,
136    RequestsSent = 100,
137    FullRequestsPerConnection = 10,
138
139    ?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
140
141    Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
142    times(RequestsSent, fun() -> spawn(Fun) end),
143
144    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),  %% Wait for everyone to get in line
145
146    ibrowse:show_dest_status("localhost", 8181),
147    Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
148    ?assertEqual(MaxSessions, length(Counts)),
149
150    ?assertEqual(lists:duplicate(MaxSessions, FullRequestsPerConnection), Counts),
151
152    Response = ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS),
153
154    ?assertEqual({error, retry_later}, Response).
155
156status() ->
157    MaxSessions = 10,
158    MaxPipeline = 10,
159    RequestsSent = 100,
160
161    Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
162    times(RequestsSent, fun() -> spawn(Fun) end),
163
164    timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),  %% Wait for everyone to get in line
165
166    ibrowse:show_dest_status(),
167    ibrowse:show_dest_status("http://localhost:8181").
168
169
170times(0, _) ->
171    ok;
172times(X, Fun) ->
173    Fun(),
174    times(X - 1, Fun).
175