Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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}.