epgsql_cth.erl 8.0 KB

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