50 |
7u83 |
1 |
-module(epgsql_cth).
|
|
|
2 |
|
|
|
3 |
-export([
|
|
|
4 |
init/2,
|
|
|
5 |
terminate/1,
|
|
|
6 |
pre_init_per_suite/3
|
|
|
7 |
]).
|
|
|
8 |
|
|
|
9 |
-include_lib("common_test/include/ct.hrl").
|
|
|
10 |
-include("epgsql_tests.hrl").
|
|
|
11 |
|
|
|
12 |
init(_Id, State) ->
|
|
|
13 |
Start = os:timestamp(),
|
|
|
14 |
PgConfig = start_postgres(),
|
|
|
15 |
ok = create_testdbs(PgConfig),
|
|
|
16 |
ct:pal(info, "postgres started in ~p ms\n",
|
|
|
17 |
[timer:now_diff(os:timestamp(), Start) / 1000]),
|
|
|
18 |
[{pg_config, PgConfig}|State].
|
|
|
19 |
|
|
|
20 |
pre_init_per_suite(_SuiteName, Config, State) ->
|
|
|
21 |
{Config ++ State, State}.
|
|
|
22 |
|
|
|
23 |
terminate(State) ->
|
|
|
24 |
ok = stop_postgres(?config(pg_config, State)).
|
|
|
25 |
|
|
|
26 |
create_testdbs(Config) ->
|
|
|
27 |
PgHost = ?config(host, Config),
|
|
|
28 |
PgPort = ?config(port, Config),
|
|
|
29 |
PgUser = ?config(user, Config),
|
|
|
30 |
Utils = ?config(utils, Config),
|
|
|
31 |
Psql = ?config(psql, Utils),
|
|
|
32 |
CreateDB = ?config(createdb, Utils),
|
|
|
33 |
|
|
|
34 |
Opts = lists:concat([" -h ", PgHost, " -p ", PgPort, " "]),
|
|
|
35 |
Cmds = [
|
|
|
36 |
[CreateDB, Opts, PgUser],
|
|
|
37 |
[Psql, Opts, "template1 < ", filename:join(?TEST_DATA_DIR, "test_schema.sql")]
|
|
|
38 |
],
|
|
|
39 |
lists:foreach(fun(Cmd) ->
|
|
|
40 |
{ok, []} = exec:run(lists:flatten(Cmd), [sync])
|
|
|
41 |
end, Cmds).
|
|
|
42 |
|
|
|
43 |
%% =============================================================================
|
|
|
44 |
%% start postgresql
|
|
|
45 |
%% =============================================================================
|
|
|
46 |
|
|
|
47 |
-define(PG_TIMEOUT, 30000).
|
|
|
48 |
|
|
|
49 |
start_postgres() ->
|
|
|
50 |
ok = application:start(erlexec),
|
|
|
51 |
pipe([
|
|
|
52 |
fun find_utils/1,
|
|
|
53 |
fun init_database/1,
|
|
|
54 |
fun get_version/1,
|
|
|
55 |
fun write_postgresql_config/1,
|
|
|
56 |
fun copy_certs/1,
|
|
|
57 |
fun write_pg_hba_config/1,
|
|
|
58 |
fun start_postgresql/1
|
|
|
59 |
], []).
|
|
|
60 |
|
|
|
61 |
stop_postgres(Config) ->
|
|
|
62 |
PgProc = ?config(proc, Config),
|
|
|
63 |
|
|
|
64 |
PgProc ! stop,
|
|
|
65 |
ok.
|
|
|
66 |
|
|
|
67 |
find_utils(State) ->
|
|
|
68 |
Utils = [initdb, createdb, postgres, psql],
|
|
|
69 |
UtilsConfig = lists:foldl(fun(U, Acc) ->
|
|
|
70 |
UList = atom_to_list(U),
|
|
|
71 |
Path = case os:find_executable(UList) of
|
|
|
72 |
false ->
|
|
|
73 |
case filelib:wildcard("/usr/lib/postgresql/**/bin/" ++ UList) of
|
|
|
74 |
[] ->
|
|
|
75 |
ct:pal(error, "~s not found", [U]),
|
|
|
76 |
throw({util_no_found, U});
|
|
|
77 |
List -> lists:last(lists:sort(List))
|
|
|
78 |
end;
|
|
|
79 |
P -> P
|
|
|
80 |
end,
|
|
|
81 |
[{U, Path}|Acc]
|
|
|
82 |
end, [], Utils),
|
|
|
83 |
[{utils, UtilsConfig}|State].
|
|
|
84 |
|
|
|
85 |
start_postgresql(Config) ->
|
|
|
86 |
PgDataDir = ?config(datadir, Config),
|
|
|
87 |
Utils = ?config(utils, Config),
|
|
|
88 |
Postgres = ?config(postgres, Utils),
|
|
|
89 |
|
|
|
90 |
PgHost = "localhost",
|
|
|
91 |
PgPort = get_free_port(),
|
|
|
92 |
SocketDir = "/tmp",
|
|
|
93 |
Command = lists:concat(
|
|
|
94 |
[Postgres,
|
|
|
95 |
" -k ", SocketDir,
|
|
|
96 |
" -D ", PgDataDir,
|
|
|
97 |
" -h ", PgHost,
|
|
|
98 |
" -p ", PgPort]),
|
|
|
99 |
ct:pal(info, ?HI_IMPORTANCE, "Starting Postgresql: `~s`", [Command]),
|
|
|
100 |
Pid = proc_lib:spawn(fun() ->
|
|
|
101 |
{ok, _, I} = exec:run_link(Command,
|
|
|
102 |
[{stderr,
|
|
|
103 |
fun(_, _, Msg) ->
|
|
|
104 |
ct:pal(info, "postgres: ~s", [Msg])
|
|
|
105 |
end},
|
|
|
106 |
{env, [{"LANGUAGE", "en"}]}]),
|
|
|
107 |
loop(I)
|
|
|
108 |
end),
|
|
|
109 |
ConfigR = [
|
|
|
110 |
{host, PgHost},
|
|
|
111 |
{port, PgPort},
|
|
|
112 |
{proc, Pid}
|
|
|
113 |
| Config
|
|
|
114 |
],
|
|
|
115 |
wait_postgresql_ready(SocketDir, ConfigR).
|
|
|
116 |
|
|
|
117 |
loop(I) ->
|
|
|
118 |
receive
|
|
|
119 |
stop -> exec:kill(I, 0);
|
|
|
120 |
_ -> loop(I)
|
|
|
121 |
end.
|
|
|
122 |
|
|
|
123 |
wait_postgresql_ready(SocketDir, Config) ->
|
|
|
124 |
PgPort = ?config(port, Config),
|
|
|
125 |
|
|
|
126 |
PgFile = lists:concat([".s.PGSQL.", PgPort]),
|
|
|
127 |
Path = filename:join(SocketDir, PgFile),
|
|
|
128 |
WaitUntil = ts_add(os:timestamp(), ?PG_TIMEOUT),
|
|
|
129 |
case wait_(Path, WaitUntil) of
|
|
|
130 |
true -> ok;
|
|
|
131 |
false -> throw(<<"Postgresql init timeout">>)
|
|
|
132 |
end,
|
|
|
133 |
Config.
|
|
|
134 |
|
|
|
135 |
wait_(Path, Until) ->
|
|
|
136 |
case file:read_file_info(Path) of
|
|
|
137 |
{error, enoent} ->
|
|
|
138 |
case os:timestamp() > Until of
|
|
|
139 |
true -> false;
|
|
|
140 |
_ ->
|
|
|
141 |
timer:sleep(300),
|
|
|
142 |
wait_(Path, Until)
|
|
|
143 |
end;
|
|
|
144 |
_ -> true
|
|
|
145 |
end.
|
|
|
146 |
|
|
|
147 |
init_database(Config) ->
|
|
|
148 |
Utils = ?config(utils, Config),
|
|
|
149 |
Initdb = ?config(initdb, Utils),
|
|
|
150 |
|
|
|
151 |
{ok, Cwd} = file:get_cwd(),
|
|
|
152 |
PgDataDir = filename:append(Cwd, "datadir"),
|
|
|
153 |
{ok, _} = exec:run(Initdb ++ " --locale en_US.UTF8 " ++ PgDataDir, [sync,stdout,stderr]),
|
|
|
154 |
[{datadir, PgDataDir}|Config].
|
|
|
155 |
|
|
|
156 |
get_version(Config) ->
|
|
|
157 |
Datadir = ?config(datadir, Config),
|
|
|
158 |
VersionFile = filename:join(Datadir, "PG_VERSION"),
|
|
|
159 |
{ok, VersionFileData} = file:read_file(VersionFile),
|
|
|
160 |
VersionBin = list_to_binary(string:strip(binary_to_list(VersionFileData), both, $\n)),
|
|
|
161 |
Version = lists:map(fun erlang:binary_to_integer/1,
|
|
|
162 |
binary:split(VersionBin, <<".">>, [global])),
|
|
|
163 |
[{version, Version} | Config].
|
|
|
164 |
|
|
|
165 |
write_postgresql_config(Config) ->
|
|
|
166 |
PgDataDir = ?config(datadir, Config),
|
|
|
167 |
|
|
|
168 |
PGConfig = [
|
|
|
169 |
"ssl = on\n",
|
|
|
170 |
"ssl_ca_file = 'root.crt'\n",
|
|
|
171 |
"lc_messages = 'en_US.UTF-8'\n",
|
|
|
172 |
"fsync = off\n",
|
|
|
173 |
"wal_level = 'logical'\n",
|
|
|
174 |
"max_replication_slots = 15\n",
|
|
|
175 |
"max_wal_senders = 15"
|
|
|
176 |
],
|
|
|
177 |
FilePath = filename:join(PgDataDir, "postgresql.conf"),
|
|
|
178 |
ok = file:write_file(FilePath, PGConfig),
|
|
|
179 |
Config.
|
|
|
180 |
|
|
|
181 |
copy_certs(Config) ->
|
|
|
182 |
PgDataDir = ?config(datadir, Config),
|
|
|
183 |
|
|
|
184 |
Files = [
|
|
|
185 |
{"server.crt", "server.crt", 8#00660},
|
|
|
186 |
{"server.key", "server.key", 8#00600},
|
|
|
187 |
{"root.crt", "root.crt", 8#00660},
|
|
|
188 |
{"root.key", "root.key", 8#00660}
|
|
|
189 |
],
|
|
|
190 |
lists:foreach(fun({From, To, Mode}) ->
|
|
|
191 |
FromPath = filename:join(?TEST_DATA_DIR, From),
|
|
|
192 |
ToPath = filename:join(PgDataDir, To),
|
|
|
193 |
{ok, _} = file:copy(FromPath, ToPath),
|
|
|
194 |
ok = file:change_mode(ToPath, Mode)
|
|
|
195 |
end, Files),
|
|
|
196 |
Config.
|
|
|
197 |
|
|
|
198 |
write_pg_hba_config(Config) ->
|
|
|
199 |
PgDataDir = ?config(datadir, Config),
|
|
|
200 |
Version = ?config(version, Config),
|
|
|
201 |
|
|
|
202 |
User = os:getenv("USER"),
|
|
|
203 |
PGConfig = [
|
|
|
204 |
"local all ", User, " trust\n",
|
|
|
205 |
"host template1 ", User, " 127.0.0.1/32 trust\n",
|
|
|
206 |
"host ", User, " ", User, " 127.0.0.1/32 trust\n",
|
|
|
207 |
"hostssl postgres ", User, " 127.0.0.1/32 trust\n",
|
|
|
208 |
"host epgsql_test_db1 ", User, " 127.0.0.1/32 trust\n",
|
|
|
209 |
"host epgsql_test_db1 epgsql_test 127.0.0.1/32 trust\n",
|
|
|
210 |
"host epgsql_test_db1 epgsql_test_md5 127.0.0.1/32 md5\n",
|
|
|
211 |
"host epgsql_test_db1 epgsql_test_cleartext 127.0.0.1/32 password\n",
|
|
|
212 |
"hostssl epgsql_test_db1 epgsql_test_cert 127.0.0.1/32 cert clientcert=1\n",
|
|
|
213 |
"host template1 ", User, " ::1/128 trust\n",
|
|
|
214 |
"host ", User, " ", User, " ::1/128 trust\n",
|
|
|
215 |
"hostssl postgres ", User, " ::1/128 trust\n",
|
|
|
216 |
"host epgsql_test_db1 ", User, " ::1/128 trust\n",
|
|
|
217 |
"host epgsql_test_db1 epgsql_test ::1/128 trust\n",
|
|
|
218 |
"host epgsql_test_db1 epgsql_test_md5 ::1/128 md5\n",
|
|
|
219 |
"host epgsql_test_db1 epgsql_test_cleartext ::1/128 password\n",
|
|
|
220 |
"hostssl epgsql_test_db1 epgsql_test_cert ::1/128 cert clientcert=1\n" |
|
|
|
221 |
case Version >= [10] of
|
|
|
222 |
true ->
|
|
|
223 |
%% See
|
|
|
224 |
%% https://www.postgresql.org/docs/10/static/release-10.html
|
|
|
225 |
%% "Change how logical replication uses pg_hba.conf"
|
|
|
226 |
["host epgsql_test_db1 epgsql_test_replication 127.0.0.1/32 trust\n",
|
|
|
227 |
%% scram auth method only available on PG >= 10
|
|
|
228 |
"host epgsql_test_db1 epgsql_test_scram 127.0.0.1/32 scram-sha-256\n"];
|
|
|
229 |
false ->
|
|
|
230 |
["host replication epgsql_test_replication 127.0.0.1/32 trust\n"]
|
|
|
231 |
end
|
|
|
232 |
],
|
|
|
233 |
FilePath = filename:join(PgDataDir, "pg_hba.conf"),
|
|
|
234 |
ok = file:write_file(FilePath, PGConfig),
|
|
|
235 |
[{user, User}|Config].
|
|
|
236 |
|
|
|
237 |
%% =============================================================================
|
|
|
238 |
%% Internal functions
|
|
|
239 |
%% =============================================================================
|
|
|
240 |
|
|
|
241 |
get_free_port() ->
|
|
|
242 |
{ok, Listen} = gen_tcp:listen(0, []),
|
|
|
243 |
{ok, Port} = inet:port(Listen),
|
|
|
244 |
ok = gen_tcp:close(Listen),
|
|
|
245 |
Port.
|
|
|
246 |
|
|
|
247 |
pipe(Funs, Config) ->
|
|
|
248 |
lists:foldl(fun(F, S) -> F(S) end, Config, Funs).
|
|
|
249 |
|
|
|
250 |
ts_add({Mega, Sec, Micro}, Timeout) ->
|
|
|
251 |
V = (Mega * 1000000 + Sec)*1000000 + Micro + Timeout * 1000,
|
|
|
252 |
{V div 1000000000000,
|
|
|
253 |
V div 1000000 rem 1000000,
|
|
|
254 |
V rem 1000000}.
|